From 158c46868ad70a3d7b0d8847022835403e14aeb2 Mon Sep 17 00:00:00 2001 From: Lion Ralfs Date: Sun, 2 Apr 2023 18:46:57 +0200 Subject: [PATCH] Fix: Exclude questionmark from URL when not sending request params (#140) * split tests between unit and integration * rename test files to include '.test.' * use mocks in tests * use separate tsconfig for tests * add (failing) tests to assert desired behavior * fix implementation * prettier-ignore the output files --- .prettierignore | 4 +- lib/collection.ts | 2 +- lib/database.ts | 19 ++---- lib/marketplace.ts | 10 +-- lib/user.ts | 8 +-- lib/util.ts | 6 +- lib/wantlist.ts | 4 +- package-lock.json | 20 ++++++ package.json | 1 + test/error.ts | 41 ----------- .../{_setup.ts => integration/_setup.test.ts} | 0 .../{client.ts => integration/client.test.ts} | 68 +------------------ .../collection.test.ts} | 17 +---- .../database.test.ts} | 4 +- test/integration/error.test.ts | 27 ++++++++ test/{lists.ts => integration/lists.test.ts} | 4 +- .../marketplace.test.ts} | 4 +- test/{oauth.ts => integration/oauth.test.ts} | 13 +--- test/{user.ts => integration/user.test.ts} | 4 +- .../wantlist.test.ts} | 4 +- test/tsconfig.json | 5 ++ test/unit/client.test.ts | 65 ++++++++++++++++++ test/unit/collections.test.ts | 47 +++++++++++++ test/unit/database.test.ts | 64 +++++++++++++++++ test/unit/error.test.ts | 16 +++++ test/unit/marketplace.test.ts | 52 ++++++++++++++ test/unit/oauth.test.ts | 9 +++ test/unit/user.test.ts | 52 ++++++++++++++ test/{util.ts => unit/util.test.ts} | 4 +- test/unit/wantlist.test.ts | 16 +++++ tsconfig.json | 6 +- 31 files changed, 423 insertions(+), 173 deletions(-) delete mode 100644 test/error.ts rename test/{_setup.ts => integration/_setup.test.ts} (100%) rename test/{client.ts => integration/client.test.ts} (81%) rename test/{collection.ts => integration/collection.test.ts} (92%) rename test/{database.ts => integration/database.test.ts} (99%) create mode 100644 test/integration/error.test.ts rename test/{lists.ts => integration/lists.test.ts} (92%) rename test/{marketplace.ts => integration/marketplace.test.ts} (99%) rename test/{oauth.ts => integration/oauth.test.ts} (86%) rename test/{user.ts => integration/user.test.ts} (97%) rename test/{wantlist.ts => integration/wantlist.test.ts} (95%) create mode 100644 test/tsconfig.json create mode 100644 test/unit/client.test.ts create mode 100644 test/unit/collections.test.ts create mode 100644 test/unit/database.test.ts create mode 100644 test/unit/error.test.ts create mode 100644 test/unit/marketplace.test.ts create mode 100644 test/unit/oauth.test.ts create mode 100644 test/unit/user.test.ts rename test/{util.ts => unit/util.test.ts} (75%) create mode 100644 test/unit/wantlist.test.ts diff --git a/.prettierignore b/.prettierignore index 007db7b..df55bc5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,4 @@ commonjs/**/*.js -coverage/ \ No newline at end of file +coverage/ +browser/index.js +node-esm/index.js diff --git a/lib/collection.ts b/lib/collection.ts index 4438e64..acc5504 100644 --- a/lib/collection.ts +++ b/lib/collection.ts @@ -184,7 +184,7 @@ export default function (client: DiscogsClient) { SortParameters<'label' | 'artist' | 'title' | 'catno' | 'format' | 'rating' | 'added' | 'year'> ): Promise> { if (client.authenticated(2) || Number(folder) === 0) { - const path = `/users/${escape(user)}/collection/folders/${folder}/releases?${toQueryString(params)}`; + const path = `/users/${escape(user)}/collection/folders/${folder}/releases${toQueryString(params)}`; return client.get(path) as Promise>; } return Promise.reject(new AuthError()); diff --git a/lib/database.ts b/lib/database.ts index 468103f..c5f747d 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -210,7 +210,7 @@ export default function (client: DiscogsClient) { artist: number | string, params?: PaginationParameters & SortParameters<'year' | 'title' | 'format'> ): Promise> { - const path = `/artists/${artist}/releases?${toQueryString(params)}`; + const path = `/artists/${artist}/releases${toQueryString(params)}`; return client.get(path) as Promise>; }, @@ -234,7 +234,7 @@ export default function (client: DiscogsClient) { ): Promise> { let path = `/releases/${release}`; if (currency !== undefined) { - path += `?${toQueryString({ curr_abbr: currency })}`; + path += `${toQueryString({ curr_abbr: currency })}`; } return client.get(path) as Promise>; }, @@ -366,7 +366,7 @@ export default function (client: DiscogsClient) { > > ): Promise> { - const path = `/masters/${master}/versions?${toQueryString(params)}`; + const path = `/masters/${master}/versions${toQueryString(params)}`; return client.get(path) as Promise>; }, @@ -399,7 +399,7 @@ export default function (client: DiscogsClient) { label: number | string, params?: PaginationParameters ): Promise> { - const path = `/labels/${label}/releases?${toQueryString(params)}`; + const path = `/labels/${label}/releases${toQueryString(params)}`; return client.get(path) as Promise>; }, @@ -436,14 +436,9 @@ export default function (client: DiscogsClient) { search: function ( params: PaginationParameters & Partial = {} ): Promise> { - const args = { ...params }; - if (args.query) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - args.q = args.query; - delete args.query; - } - return client.get({ url: `/database/search?${toQueryString(args)}`, authLevel: 1 }) as Promise< + const { query, ...inputArgs } = params; + const args = query ? Object.assign(inputArgs, { q: query }) : inputArgs; + return client.get({ url: `/database/search${toQueryString(args)}`, authLevel: 1 }) as Promise< RateLimitedResponse >; }, diff --git a/lib/marketplace.ts b/lib/marketplace.ts index 6458f8e..c52f812 100644 --- a/lib/marketplace.ts +++ b/lib/marketplace.ts @@ -101,7 +101,7 @@ export default function (client: DiscogsClient) { getListing: function (listing: number, currency?: Currency): Promise> { let path = `/marketplace/listings/${listing}`; if (currency !== undefined) { - path += `?${toQueryString({ curr_abbr: currency })}`; + path += `${toQueryString({ curr_abbr: currency })}`; } return client.get(path) as Promise>; }, @@ -229,7 +229,7 @@ export default function (client: DiscogsClient) { PaginationParameters & SortParameters<'id' | 'buyer' | 'created' | 'status' | 'last_activity'> ): Promise }>> { - const path = `/marketplace/orders?${toQueryString(params)}`; + const path = `/marketplace/orders${toQueryString(params)}`; return client.get({ url: path, authLevel: 2 }) as Promise< RateLimitedResponse }> >; @@ -286,7 +286,7 @@ export default function (client: DiscogsClient) { order: number, params?: PaginationParameters ): Promise }>> { - const path = `/marketplace/orders/${order}/messages?${toQueryString(params)}`; + const path = `/marketplace/orders/${order}/messages${toQueryString(params)}`; return client.get({ url: path, authLevel: 2 }) as Promise< RateLimitedResponse }> >; @@ -370,9 +370,9 @@ export default function (client: DiscogsClient) { ): Promise> { let path = `/marketplace/stats/${release}`; if (currency) { - path += `?${toQueryString({ curr_abbr: currency })}`; + path += `${toQueryString({ curr_abbr: currency })}`; } - return client.get({ url: path }) as Promise>; + return client.get(path) as Promise>; }, }; } diff --git a/lib/user.ts b/lib/user.ts index 2f10d63..2d8525b 100644 --- a/lib/user.ts +++ b/lib/user.ts @@ -144,7 +144,7 @@ export default function (client: DiscogsClient) { 'listed' | 'price' | 'item' | 'artist' | 'label' | 'catno' | 'audio' | 'status' | 'location' > ): Promise> { - const path = `/users/${escape(user)}/inventory?${toQueryString(params)}`; + const path = `/users/${escape(user)}/inventory${toQueryString(params)}`; return client.get(path) as Promise>; }, @@ -198,7 +198,7 @@ export default function (client: DiscogsClient) { params?: PaginationParameters & SortParameters<'label' | 'artist' | 'title' | 'catno' | 'format' | 'rating' | 'year' | 'added'> ): Promise> { - const path = `/users/${escape(user)}/contributions?${toQueryString(params)}`; + const path = `/users/${escape(user)}/contributions${toQueryString(params)}`; return client.get(path) as Promise>; }, @@ -217,7 +217,7 @@ export default function (client: DiscogsClient) { user: string, params?: PaginationParameters ): Promise> { - const path = `/users/${escape(user)}/submissions?${toQueryString(params)}`; + const path = `/users/${escape(user)}/submissions${toQueryString(params)}`; return client.get(path) as Promise>; }, @@ -236,7 +236,7 @@ export default function (client: DiscogsClient) { user: string, params?: PaginationParameters ): Promise> { - const path = `/users/${escape(user)}/lists?${toQueryString(params)}`; + const path = `/users/${escape(user)}/lists${toQueryString(params)}`; return client.get(path) as Promise>; }, }; diff --git a/lib/util.ts b/lib/util.ts index 2bc87c4..cc68b7e 100644 --- a/lib/util.ts +++ b/lib/util.ts @@ -8,12 +8,12 @@ export function stripVariation(name: string): string { } /** - * Turns a simple key-value object into a query string + * Turns a simple key-value object into a query string (including the leading questionmark) * @param {Record} data - Data object containing the params * @returns {string} */ export function toQueryString(data?: Record): string { - if (!data) { + if (!data || !Object.keys(data).length) { return ''; } @@ -21,7 +21,7 @@ export function toQueryString(data?: Record }>> { - const path = `/users/${escape(user)}/wants?${toQueryString(params)}`; + const path = `/users/${escape(user)}/wants${toQueryString(params)}`; return client.get(path) as Promise< RateLimitedResponse }> diff --git a/package-lock.json b/package-lock.json index 024b958..818d756 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ }, "devDependencies": { "@esbuild-kit/esm-loader": "^2.5.0", + "@fluffy-spoon/substitute": "^1.208.0", "@typescript-eslint/eslint-plugin": "^5.41.0", "@typescript-eslint/parser": "^5.41.0", "ava": "^5.0.1", @@ -592,6 +593,19 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fluffy-spoon/substitute": { + "version": "1.208.0", + "resolved": "https://registry.npmjs.org/@fluffy-spoon/substitute/-/substitute-1.208.0.tgz", + "integrity": "sha512-BU5vKRoK4OYlKzDtyg4HbtWnUNLOvV0ntqEZIphz+mq2G0HlVFywwJ7M+FbIcnJVDbUReS01FyL5x8R01r7zBg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/substitute-js#section-contribute" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -9368,6 +9382,12 @@ "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", "dev": true }, + "@fluffy-spoon/substitute": { + "version": "1.208.0", + "resolved": "https://registry.npmjs.org/@fluffy-spoon/substitute/-/substitute-1.208.0.tgz", + "integrity": "sha512-BU5vKRoK4OYlKzDtyg4HbtWnUNLOvV0ntqEZIphz+mq2G0HlVFywwJ7M+FbIcnJVDbUReS01FyL5x8R01r7zBg==", + "dev": true + }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", diff --git a/package.json b/package.json index c91bbaf..aa0aec9 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "devDependencies": { "@esbuild-kit/esm-loader": "^2.5.0", + "@fluffy-spoon/substitute": "^1.208.0", "@typescript-eslint/eslint-plugin": "^5.41.0", "@typescript-eslint/parser": "^5.41.0", "ava": "^5.0.1", diff --git a/test/error.ts b/test/error.ts deleted file mode 100644 index 8572e9e..0000000 --- a/test/error.ts +++ /dev/null @@ -1,41 +0,0 @@ -import test from 'ava'; -import { rest } from 'msw'; -import { DiscogsClient } from '../lib/client.js'; -import { DiscogsError, AuthError } from '../lib/error.js'; -import { setupMockAPI } from './_setup.js'; - -const server = setupMockAPI(); - -test('Error: Test DiscogsError', t => { - const discogsError = new DiscogsError(403, 'Test'); - // t.true(discogsError instanceof DiscogsError, 'Instance of DiscogsError'); - t.true(discogsError instanceof Error, 'Instance of Error'); - t.is(discogsError.statusCode, 403, 'Status code === 403'); -}); - -test('Error: Test AuthError', t => { - const authError = new AuthError(); - // t.true(authError instanceof AuthError, 'Instance of AuthError'); - t.true(authError instanceof Error, 'Instance of Error'); - t.is(authError.statusCode, 401, 'Status code === 401'); -}); - -test.serial('Error: Passed an instance of DiscogsError when bad status code', async t => { - t.plan(4); - - server.use( - rest.get('https://api.discogs.com/labels/1123123123123/releases', (req, res, ctx) => { - t.pass(); - return res(ctx.status(404), ctx.json({ message: 'error message' })); - }) - ); - - const client = new DiscogsClient(); - try { - await client.database().getLabelReleases(1123123123123, { page: 3, per_page: 25 }); - } catch (err: any) { - t.is(err.statusCode, 404); - t.is(err.message, 'error message'); - t.true(err instanceof DiscogsError); - } -}); diff --git a/test/_setup.ts b/test/integration/_setup.test.ts similarity index 100% rename from test/_setup.ts rename to test/integration/_setup.test.ts diff --git a/test/client.ts b/test/integration/client.test.ts similarity index 81% rename from test/client.ts rename to test/integration/client.test.ts index 014ce02..bb1d248 100644 --- a/test/client.ts +++ b/test/integration/client.test.ts @@ -1,36 +1,10 @@ import test from 'ava'; import { rest } from 'msw'; -import { DiscogsClient } from '../lib/client.js'; -import { setupMockAPI } from './_setup.js'; +import { DiscogsClient } from '@lib/client.js'; +import { setupMockAPI } from './_setup.test.js'; const server = setupMockAPI(); -test('DiscogsClient: Test instance', t => { - t.true(new DiscogsClient() instanceof DiscogsClient); -}); - -test('DiscogsClient: Test authenticated()', t => { - t.false(new DiscogsClient().authenticated(1), 'Authentication level 1 === false'); -}); - -test('DiscogsClient: Test setConfig with exponential backoff parameters', t => { - // Given - const client = new DiscogsClient(); - - // When - client.setConfig({ - exponentialBackoffMaxRetries: 333, - exponentialBackoffIntervalMs: 444, - exponentialBackoffRate: 555, - }); - - // Then - - t.is(client['config'].exponentialBackoffMaxRetries, 333); - t.is(client['config'].exponentialBackoffIntervalMs, 444); - t.is(client['config'].exponentialBackoffRate, 555); -}); - test.serial('DiscogsClient: Test get()', async t => { t.plan(1); server.use( @@ -127,44 +101,6 @@ test.serial('DiscogsClient: Auth (userToken)', async t => { await client.getIdentity(); }); -test('DiscogsClient: Auth (Full OAuth)', t => { - const client = new DiscogsClient({ - auth: { - method: 'oauth', - consumerKey: 'consumerKey', - consumerSecret: 'consumerSecret', - accessToken: 'accessToken', - accessTokenSecret: 'accessTokenSecret', - }, - }); - - t.deepEqual(client['auth'], { - method: 'oauth', - level: 2, - consumerKey: 'consumerKey', - consumerSecret: 'consumerSecret', - accessToken: 'accessToken', - accessTokenSecret: 'accessTokenSecret', - }); -}); - -test('DiscogsClient: Auth (OAuth without tokens)', t => { - const client = new DiscogsClient({ - auth: { - method: 'oauth', - consumerKey: 'consumerKey', - consumerSecret: 'consumerSecret', - }, - }); - - t.deepEqual(client['auth'], { - method: 'oauth', - level: 1, - consumerKey: 'consumerKey', - consumerSecret: 'consumerSecret', - }); -}); - test.serial('DiscogsClient: Sends OAuth header', async t => { t.plan(1); diff --git a/test/collection.ts b/test/integration/collection.test.ts similarity index 92% rename from test/collection.ts rename to test/integration/collection.test.ts index 0dfe03b..2bd2291 100644 --- a/test/collection.ts +++ b/test/integration/collection.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; import { rest } from 'msw'; -import { DiscogsClient } from '../lib/client.js'; -import { setupMockAPI } from './_setup.js'; +import { DiscogsClient } from '@lib/client.js'; +import { setupMockAPI } from './_setup.test.js'; const server = setupMockAPI(); @@ -53,11 +53,6 @@ test.serial('Collection: Get folder metadata (no auth required for public folder await client.user().collection().getFolder('rodneyfool', 0); }); -test('Collection: Get folder metadata (throws auth error)', async t => { - const client = new DiscogsClient(); - await t.throwsAsync(client.user().collection().getFolder('rodneyfool', 1234)); -}); - test.serial('Collection: Edit folder name', async t => { t.plan(1); server.use( @@ -130,14 +125,6 @@ test.serial('Collection: Collection items by folder (default doesnt need auth)', await client.user().collection().getReleases('rodneyfool', '0', { sort: 'artist', sort_order: 'desc' }); }); -test('Collection: Collection items by folder (throws auth error)', async t => { - t.plan(1); - const client = new DiscogsClient(); - await t.throwsAsync( - client.user().collection().getReleases('rodneyfool', '1234', { sort: 'artist', sort_order: 'desc' }) - ); -}); - test.serial('Collection: Add release to folder', async t => { t.plan(1); server.use( diff --git a/test/database.ts b/test/integration/database.test.ts similarity index 99% rename from test/database.ts rename to test/integration/database.test.ts index c24437d..1227e43 100644 --- a/test/database.ts +++ b/test/integration/database.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; import { rest } from 'msw'; -import { DiscogsClient } from '../lib/client.js'; -import { setupMockAPI } from './_setup.js'; +import { DiscogsClient } from '@lib/client.js'; +import { setupMockAPI } from './_setup.test.js'; const server = setupMockAPI(); diff --git a/test/integration/error.test.ts b/test/integration/error.test.ts new file mode 100644 index 0000000..c838885 --- /dev/null +++ b/test/integration/error.test.ts @@ -0,0 +1,27 @@ +import test from 'ava'; +import { rest } from 'msw'; +import { DiscogsClient } from '@lib/client.js'; +import { DiscogsError } from '@lib/error.js'; +import { setupMockAPI } from './_setup.test.js'; + +const server = setupMockAPI(); + +test.serial('Error: Passed an instance of DiscogsError when bad status code', async t => { + t.plan(4); + + server.use( + rest.get('https://api.discogs.com/labels/1123123123123/releases', (req, res, ctx) => { + t.pass(); + return res(ctx.status(404), ctx.json({ message: 'error message' })); + }) + ); + + const client = new DiscogsClient(); + try { + await client.database().getLabelReleases(1123123123123, { page: 3, per_page: 25 }); + } catch (err: any) { + t.is(err.statusCode, 404); + t.is(err.message, 'error message'); + t.true(err instanceof DiscogsError); + } +}); diff --git a/test/lists.ts b/test/integration/lists.test.ts similarity index 92% rename from test/lists.ts rename to test/integration/lists.test.ts index f3ef1ce..1a824e8 100644 --- a/test/lists.ts +++ b/test/integration/lists.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; import { rest } from 'msw'; -import { DiscogsClient } from '../lib/client.js'; -import { setupMockAPI } from './_setup.js'; +import { DiscogsClient } from '@lib/client.js'; +import { setupMockAPI } from './_setup.test.js'; const server = setupMockAPI(); diff --git a/test/marketplace.ts b/test/integration/marketplace.test.ts similarity index 99% rename from test/marketplace.ts rename to test/integration/marketplace.test.ts index a14455d..c99d13b 100644 --- a/test/marketplace.ts +++ b/test/integration/marketplace.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; import { rest } from 'msw'; -import { DiscogsClient } from '../lib/client.js'; -import { setupMockAPI } from './_setup.js'; +import { DiscogsClient } from '@lib/client.js'; +import { setupMockAPI } from './_setup.test.js'; const server = setupMockAPI(); diff --git a/test/oauth.ts b/test/integration/oauth.test.ts similarity index 86% rename from test/oauth.ts rename to test/integration/oauth.test.ts index c51498e..e9cc430 100644 --- a/test/oauth.ts +++ b/test/integration/oauth.test.ts @@ -1,8 +1,8 @@ import test from 'ava'; import { rest } from 'msw'; -import { DiscogsError } from '../lib/error.js'; -import { DiscogsOAuth, toAuthHeader } from '../lib/oauth.js'; -import { setupMockAPI } from './_setup.js'; +import { DiscogsError } from '@lib/error.js'; +import { DiscogsOAuth } from '@lib/oauth.js'; +import { setupMockAPI } from './_setup.test.js'; const server = setupMockAPI(); @@ -94,10 +94,3 @@ test.serial('OAuth: Get an access token (error)', async t => { } ); }); - -test('OAuth: toAuthHeader', t => { - t.regex( - toAuthHeader('consumer_key', 'consumer_secret', 'access_token', 'access_token_secret'), - /^OAuth oauth_consumer_key="consumer_key", oauth_token="access_token", oauth_signature_method="PLAINTEXT", oauth_signature="consumer_secret&access_token_secret", oauth_timestamp="\d+", oauth_nonce=".+", oauth_token_secret="access_token_secret", oauth_version="1.0"$/ - ); -}); diff --git a/test/user.ts b/test/integration/user.test.ts similarity index 97% rename from test/user.ts rename to test/integration/user.test.ts index 9f385b6..fc81f05 100644 --- a/test/user.ts +++ b/test/integration/user.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; import { rest } from 'msw'; -import { DiscogsClient } from '../lib/client.js'; -import { setupMockAPI } from './_setup.js'; +import { DiscogsClient } from '@lib/client.js'; +import { setupMockAPI } from './_setup.test.js'; const server = setupMockAPI(); diff --git a/test/wantlist.ts b/test/integration/wantlist.test.ts similarity index 95% rename from test/wantlist.ts rename to test/integration/wantlist.test.ts index 27c6c7c..9d0b50c 100644 --- a/test/wantlist.ts +++ b/test/integration/wantlist.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; import { rest } from 'msw'; -import { DiscogsClient } from '../lib/client.js'; -import { setupMockAPI } from './_setup.js'; +import { DiscogsClient } from '@lib/client.js'; +import { setupMockAPI } from './_setup.test.js'; const server = setupMockAPI(); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..f54ed9a --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.json", + "include": ["../lib/**/*", "../test/**/*"], + "exclude": [] +} diff --git a/test/unit/client.test.ts b/test/unit/client.test.ts new file mode 100644 index 0000000..c7307ae --- /dev/null +++ b/test/unit/client.test.ts @@ -0,0 +1,65 @@ +import test from 'ava'; +import { DiscogsClient } from '@lib/client.js'; + +test('DiscogsClient: Test instance', t => { + t.true(new DiscogsClient() instanceof DiscogsClient); +}); + +test('DiscogsClient: Test authenticated()', t => { + t.false(new DiscogsClient().authenticated(1), 'Authentication level 1 === false'); +}); + +test('DiscogsClient: Test setConfig with exponential backoff parameters', t => { + // Given + const client = new DiscogsClient(); + + // When + client.setConfig({ + exponentialBackoffMaxRetries: 333, + exponentialBackoffIntervalMs: 444, + exponentialBackoffRate: 555, + }); + + // Then + t.is(client['config'].exponentialBackoffMaxRetries, 333); + t.is(client['config'].exponentialBackoffIntervalMs, 444); + t.is(client['config'].exponentialBackoffRate, 555); +}); + +test('DiscogsClient: Auth (Full OAuth)', t => { + const client = new DiscogsClient({ + auth: { + method: 'oauth', + consumerKey: 'consumerKey', + consumerSecret: 'consumerSecret', + accessToken: 'accessToken', + accessTokenSecret: 'accessTokenSecret', + }, + }); + + t.deepEqual(client['auth'], { + method: 'oauth', + level: 2, + consumerKey: 'consumerKey', + consumerSecret: 'consumerSecret', + accessToken: 'accessToken', + accessTokenSecret: 'accessTokenSecret', + }); +}); + +test('DiscogsClient: Auth (OAuth without tokens)', t => { + const client = new DiscogsClient({ + auth: { + method: 'oauth', + consumerKey: 'consumerKey', + consumerSecret: 'consumerSecret', + }, + }); + + t.deepEqual(client['auth'], { + method: 'oauth', + level: 1, + consumerKey: 'consumerKey', + consumerSecret: 'consumerSecret', + }); +}); diff --git a/test/unit/collections.test.ts b/test/unit/collections.test.ts new file mode 100644 index 0000000..120bc34 --- /dev/null +++ b/test/unit/collections.test.ts @@ -0,0 +1,47 @@ +import test from 'ava'; +import collectionFactory from '@lib/collection.js'; +import type { DiscogsClient } from '@lib/client.js'; +import { Substitute } from '@fluffy-spoon/substitute'; + +test('Collection: Get folder metadata (throws auth error because not authenticated and requesting folder_id != 0)', async t => { + // Given + const client = Substitute.for(); + const collection = collectionFactory(client); + client.authenticated(2).returns(false); + + // When/Then + await t.throwsAsync(collection.getFolder('rodneyfool', 1234)); +}); + +test('Collection: Get folder releases (throws auth error because not authenticated and requesting folder_id != 0)', async t => { + // Given + const client = Substitute.for(); + const collection = collectionFactory(client); + client.authenticated(2).returns(false); + + // When/Then + await t.throwsAsync(collection.getReleases('rodneyfool', 1234)); +}); + +test('Collection: Collection items by folder (throws auth error)', async t => { + // Given + const client = Substitute.for(); + const collection = collectionFactory(client); + client.authenticated(2).returns(false); + + // When/Then + await t.throwsAsync(collection.getReleases('rodneyfool', '1234', { sort: 'artist', sort_order: 'desc' })); +}); + +test('Collection (getReleases): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const collection = collectionFactory(client); + client.authenticated(2).returns(true); + + // When + await collection.getReleases('some-user', 123); + + // Then + t.notThrows(() => client.received().get(`/users/some-user/collection/folders/123/releases`)); +}); diff --git a/test/unit/database.test.ts b/test/unit/database.test.ts new file mode 100644 index 0000000..c29790c --- /dev/null +++ b/test/unit/database.test.ts @@ -0,0 +1,64 @@ +import test from 'ava'; +import databaseFactory from '@lib/database.js'; +import type { DiscogsClient } from '@lib/client.js'; +import { Substitute } from '@fluffy-spoon/substitute'; + +test('Database (getArtistReleases): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const database = databaseFactory(client); + + // When + await database.getArtistReleases(108713); + + // Then + t.notThrows(() => client.received().get(`/artists/108713/releases`)); +}); + +test('Database (getRelease): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const database = databaseFactory(client); + + // When + await database.getRelease(108713); + + // Then + t.notThrows(() => client.received().get(`/releases/108713`)); +}); + +test('Database (getMasterVersions): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const database = databaseFactory(client); + + // When + await database.getMasterVersions(108713); + + // Then + t.notThrows(() => client.received().get(`/masters/108713/versions`)); +}); + +test('Database (getLabelReleases): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const database = databaseFactory(client); + + // When + await database.getLabelReleases(108713); + + // Then + t.notThrows(() => client.received().get(`/labels/108713/releases`)); +}); + +test('Database (search): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const database = databaseFactory(client); + + // When + await database.search(); + + // Then + t.notThrows(() => client.received().get({ url: `/database/search`, authLevel: 1 })); +}); diff --git a/test/unit/error.test.ts b/test/unit/error.test.ts new file mode 100644 index 0000000..e5c7372 --- /dev/null +++ b/test/unit/error.test.ts @@ -0,0 +1,16 @@ +import test from 'ava'; +import { DiscogsError, AuthError } from '@lib/error.js'; + +test('Error: Test DiscogsError', t => { + const discogsError = new DiscogsError(403, 'Test'); + // t.true(discogsError instanceof DiscogsError, 'Instance of DiscogsError'); + t.true(discogsError instanceof Error, 'Instance of Error'); + t.is(discogsError.statusCode, 403, 'Status code === 403'); +}); + +test('Error: Test AuthError', t => { + const authError = new AuthError(); + // t.true(authError instanceof AuthError, 'Instance of AuthError'); + t.true(authError instanceof Error, 'Instance of Error'); + t.is(authError.statusCode, 401, 'Status code === 401'); +}); diff --git a/test/unit/marketplace.test.ts b/test/unit/marketplace.test.ts new file mode 100644 index 0000000..b2dc1b4 --- /dev/null +++ b/test/unit/marketplace.test.ts @@ -0,0 +1,52 @@ +import test from 'ava'; +import marketplaceFactory from '@lib/marketplace.js'; +import type { DiscogsClient } from '@lib/client.js'; +import { Substitute } from '@fluffy-spoon/substitute'; + +test('Marketplace (getListing): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const marketplace = marketplaceFactory(client); + + // When + await marketplace.getListing(172723812); + + // Then + t.notThrows(() => client.received().get(`/marketplace/listings/172723812`)); +}); + +test('Marketplace (getOrders): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const marketplace = marketplaceFactory(client); + + // When + await marketplace.getOrders(); + + // Then + t.notThrows(() => client.received().get({ url: `/marketplace/orders`, authLevel: 2 })); +}); + +test('Marketplace (getOrderMessages): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const marketplace = marketplaceFactory(client); + + // When + await marketplace.getOrderMessages(1); + + // Then + t.notThrows(() => client.received().get({ url: `/marketplace/orders/1/messages`, authLevel: 2 })); +}); + +test('Marketplace (getReleaseStats): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const marketplace = marketplaceFactory(client); + + // When + await marketplace.getReleaseStats(1); + + // Then + t.notThrows(() => client.received().get(`/marketplace/stats/1`)); +}); diff --git a/test/unit/oauth.test.ts b/test/unit/oauth.test.ts new file mode 100644 index 0000000..3ec95bf --- /dev/null +++ b/test/unit/oauth.test.ts @@ -0,0 +1,9 @@ +import test from 'ava'; +import { toAuthHeader } from '@lib/oauth.js'; + +test('OAuth: toAuthHeader', t => { + t.regex( + toAuthHeader('consumer_key', 'consumer_secret', 'access_token', 'access_token_secret'), + /^OAuth oauth_consumer_key="consumer_key", oauth_token="access_token", oauth_signature_method="PLAINTEXT", oauth_signature="consumer_secret&access_token_secret", oauth_timestamp="\d+", oauth_nonce=".+", oauth_token_secret="access_token_secret", oauth_version="1.0"$/ + ); +}); diff --git a/test/unit/user.test.ts b/test/unit/user.test.ts new file mode 100644 index 0000000..3ce6fb7 --- /dev/null +++ b/test/unit/user.test.ts @@ -0,0 +1,52 @@ +import test from 'ava'; +import userFactory from '@lib/user.js'; +import type { DiscogsClient } from '@lib/client.js'; +import { Substitute } from '@fluffy-spoon/substitute'; + +test('User (getInventory): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const user = userFactory(client); + + // When + await user.getInventory('rodneyfool'); + + // Then + t.notThrows(() => client.received().get(`/users/rodneyfool/inventory`)); +}); + +test('User (getContributions): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const user = userFactory(client); + + // When + await user.getContributions('rodneyfool'); + + // Then + t.notThrows(() => client.received().get(`/users/rodneyfool/contributions`)); +}); + +test('User (getSubmissions): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const user = userFactory(client); + + // When + await user.getSubmissions('rodneyfool'); + + // Then + t.notThrows(() => client.received().get(`/users/rodneyfool/submissions`)); +}); + +test('User (getLists): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const user = userFactory(client); + + // When + await user.getLists('rodneyfool'); + + // Then + t.notThrows(() => client.received().get(`/users/rodneyfool/lists`)); +}); \ No newline at end of file diff --git a/test/util.ts b/test/unit/util.test.ts similarity index 75% rename from test/util.ts rename to test/unit/util.test.ts index be984d2..4cd0c46 100644 --- a/test/util.ts +++ b/test/unit/util.test.ts @@ -1,5 +1,5 @@ import test from 'ava'; -import { stripVariation, escape, toQueryString } from '../lib/util.js'; +import { stripVariation, escape, toQueryString } from '@lib/util.js'; test('Util: Test stripVariation()', t => { const stripped = stripVariation('Artist (2)'); @@ -13,5 +13,5 @@ test('Util: Test escape()', t => { test('Util: Test toQueryString()', t => { t.is(toQueryString(), ''); - t.is(toQueryString({ foo: 'bar', baz: 1 }), 'foo=bar&baz=1'); + t.is(toQueryString({ foo: 'bar', baz: 1 }), '?foo=bar&baz=1'); }); diff --git a/test/unit/wantlist.test.ts b/test/unit/wantlist.test.ts new file mode 100644 index 0000000..c80bfa0 --- /dev/null +++ b/test/unit/wantlist.test.ts @@ -0,0 +1,16 @@ +import test from 'ava'; +import wantlistFactory from '@lib/wantlist.js'; +import type { DiscogsClient } from '@lib/client.js'; +import { Substitute } from '@fluffy-spoon/substitute'; + +test('Wantlist (getReleases): Should not send query params when requesting without pagination', async t => { + // Given + const client = Substitute.for(); + const wantlist = wantlistFactory(client); + + // When + await wantlist.getReleases('rodneyfool'); + + // Then + t.notThrows(() => client.received().get(`/users/rodneyfool/wants`)); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 0a74b0b..856bcd1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,10 @@ "lib": ["dom", "dom.iterable", "esnext"], "downlevelIteration": true, "typeRoots": ["./test/@types", "./node_modules/@types"], - "skipLibCheck": true + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@lib/*": ["lib/*"] + } } }