commit
391915eb09
13 changed files with 6104 additions and 90 deletions
32
__mocks__/obsidian.ts
Normal file
32
__mocks__/obsidian.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { RequestUrlParam, RequestUrlResponse } from "obsidian";
|
||||||
|
|
||||||
|
export function requestUrl(request: RequestUrlParam): Promise<RequestUrlResponse> {
|
||||||
|
return fetch(request.url, {
|
||||||
|
method: request.method,
|
||||||
|
headers: request.headers,
|
||||||
|
body: request.body,
|
||||||
|
}).then(async (response) => {
|
||||||
|
if (response.status >= 400 && request.throw) {
|
||||||
|
throw new Error(`Request failed, ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn response headers into Record<string, string> object
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
response.headers.forEach((value, key) => {
|
||||||
|
headers[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const arraybuffer = await response.arrayBuffer();
|
||||||
|
const text = arraybuffer ? new TextDecoder().decode(arraybuffer) : '';
|
||||||
|
const json = text ? JSON.parse(text) : {};
|
||||||
|
|
||||||
|
let response_body: RequestUrlResponse = {
|
||||||
|
status: response.status,
|
||||||
|
headers: headers,
|
||||||
|
arrayBuffer: arraybuffer,
|
||||||
|
json: json,
|
||||||
|
text: text,
|
||||||
|
};
|
||||||
|
return response_body;
|
||||||
|
});
|
||||||
|
}
|
||||||
13
jest.config.js
Normal file
13
jest.config.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
module.exports = {
|
||||||
|
"roots": [
|
||||||
|
"<rootDir>/src",
|
||||||
|
"<rootDir>"
|
||||||
|
],
|
||||||
|
"testMatch": [
|
||||||
|
"**/__tests__/**/*.+(ts|tsx|js)",
|
||||||
|
"**/?(*.)+(spec|test).+(ts|tsx|js)"
|
||||||
|
],
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(ts|tsx)$": "ts-jest"
|
||||||
|
},
|
||||||
|
}
|
||||||
5813
package-lock.json
generated
5813
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
|
@ -6,20 +6,29 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node esbuild.config.mjs",
|
||||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Moritz Jung",
|
"author": "Moritz Jung",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@popperjs/core": "^2.11.5",
|
"@popperjs/core": "^2.11.5",
|
||||||
|
"@types/jest": "^28.1.3",
|
||||||
"@types/node": "^16.11.6",
|
"@types/node": "^16.11.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.2.0",
|
"@typescript-eslint/eslint-plugin": "^5.2.0",
|
||||||
"@typescript-eslint/parser": "^5.2.0",
|
"@typescript-eslint/parser": "^5.2.0",
|
||||||
"builtin-modules": "^3.2.0",
|
"builtin-modules": "^3.2.0",
|
||||||
"esbuild": "0.13.12",
|
"esbuild": "0.13.12",
|
||||||
|
"jest": "^28.1.2",
|
||||||
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
|
"ts-jest": "^28.0.5",
|
||||||
"tslib": "2.3.1",
|
"tslib": "2.3.1",
|
||||||
"typescript": "4.4.4"
|
"typescript": "4.4.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ts-node": "^10.8.1",
|
||||||
|
"yarn": "^1.22.19"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
src/tests/ParameterizedAPI.test.ts
Normal file
110
src/tests/ParameterizedAPI.test.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import fetchMock, { enableFetchMocks } from 'jest-fetch-mock';
|
||||||
|
import { MediaDbPluginSettings } from 'src/settings/Settings';
|
||||||
|
import { LocGovAPI } from '../api/apis/LocGovAPI';
|
||||||
|
import { MALAPI } from '../api/apis/MALAPI';
|
||||||
|
import { MusicBrainzAPI } from '../api/apis/MusicBrainzAPI';
|
||||||
|
import { OMDbAPI } from '../api/apis/OMDbAPI';
|
||||||
|
import { SteamAPI } from '../api/apis/SteamAPI';
|
||||||
|
import { WikipediaAPI } from '../api/apis/WikipediaAPI';
|
||||||
|
import MediaDbPlugin from '../main';
|
||||||
|
import { setMALResponseMock, setMusicBrainzResponseMock, setOMDbResponseMock, setSteamResponseMock, setWikipediaResponseMock } from "./mockHelpers";
|
||||||
|
import MALMockMovie from "./ResponseMocks/MALMockMovie.json";
|
||||||
|
import MusicBrainzResponseMock from "./ResponseMocks/MusicBrainzMockResponse.json";
|
||||||
|
import OMDBMockMovie from "./ResponseMocks/OMDBMockResponse.json";
|
||||||
|
import SteamAPIResponseMock from "./ResponseMocks/SteamAPIMockResponse.json";
|
||||||
|
import WikipediaMockResponse from "./ResponseMocks/WikipediaMockResponse.json";
|
||||||
|
|
||||||
|
enableFetchMocks();
|
||||||
|
export let apiMock: OMDbAPI | MALAPI | LocGovAPI | MusicBrainzAPI | SteamAPI | WikipediaAPI;
|
||||||
|
|
||||||
|
describe.each(
|
||||||
|
[
|
||||||
|
{ name: OMDbAPI },
|
||||||
|
{ name: MALAPI },
|
||||||
|
{ name: LocGovAPI },
|
||||||
|
{ name: MusicBrainzAPI },
|
||||||
|
{ name: SteamAPI },
|
||||||
|
{ name: WikipediaAPI }
|
||||||
|
]
|
||||||
|
)('$name.name', ({ name: parameterizedApi }) => {
|
||||||
|
beforeAll(() => {
|
||||||
|
let settingsMock: MediaDbPluginSettings = {} as MediaDbPluginSettings;
|
||||||
|
let pluginMock = {} as MediaDbPlugin;
|
||||||
|
pluginMock.settings = settingsMock;
|
||||||
|
// TODO: add fake API key?
|
||||||
|
apiMock = new parameterizedApi(pluginMock);
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchMock.resetMocks();
|
||||||
|
})
|
||||||
|
|
||||||
|
test("searchByTitle behavior when API returns garbage data", async () => {
|
||||||
|
const garbageResponse = JSON.stringify({
|
||||||
|
data: "string"
|
||||||
|
});
|
||||||
|
fetchMock.mockResponseOnce(garbageResponse)
|
||||||
|
await expect(apiMock.searchByTitle("sample")).resolves.toEqual([]);
|
||||||
|
// }
|
||||||
|
expect(fetch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("searchByTitle behavior when requestUrl/fetch returns 401", async () => {
|
||||||
|
let sampleResponse = {
|
||||||
|
data: "string"
|
||||||
|
};
|
||||||
|
fetchMock.mockResponse(JSON.stringify(sampleResponse), { status: 401 });
|
||||||
|
// TODO: Check API name and fix message
|
||||||
|
// TODO: Externalize string
|
||||||
|
await expect(apiMock.searchByTitle("sample")).rejects.toThrow(`MDB | Received status code ${401} from an API.`);
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("searchByTitle behavior when requestUrl/fetch returns 403", async () => {
|
||||||
|
let sampleResponse = {
|
||||||
|
data: "string"
|
||||||
|
};
|
||||||
|
fetchMock.mockResponse(JSON.stringify(sampleResponse), { status: 403 });
|
||||||
|
// TODO: Check API name and fix message
|
||||||
|
// TODO: Externalize string/import?
|
||||||
|
await expect(apiMock.searchByTitle("sample")).rejects.toThrow(`MDB | Received status code ${403} from an API.`);
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("searchByTitle behavior when requestUrl/fetch returns 200", async () => {
|
||||||
|
let sampleResponse;
|
||||||
|
let ret;
|
||||||
|
switch (parameterizedApi) {
|
||||||
|
case OMDbAPI:
|
||||||
|
ret = setOMDbResponseMock();
|
||||||
|
sampleResponse = OMDBMockMovie;
|
||||||
|
break;
|
||||||
|
case WikipediaAPI:
|
||||||
|
ret = setWikipediaResponseMock();
|
||||||
|
sampleResponse = WikipediaMockResponse;
|
||||||
|
break;
|
||||||
|
case MALAPI:
|
||||||
|
// TODO: MAL needs more tests for different types of content
|
||||||
|
ret = setMALResponseMock();
|
||||||
|
sampleResponse = MALMockMovie;
|
||||||
|
case LocGovAPI:
|
||||||
|
// TODO: Add soon
|
||||||
|
break;
|
||||||
|
case SteamAPI:
|
||||||
|
sampleResponse = SteamAPIResponseMock;
|
||||||
|
ret = setSteamResponseMock();
|
||||||
|
break;
|
||||||
|
case MusicBrainzAPI:
|
||||||
|
sampleResponse = MusicBrainzResponseMock;
|
||||||
|
ret = setMusicBrainzResponseMock();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
fetchMock.mockResponse(JSON.stringify(sampleResponse), { status: 200 });
|
||||||
|
// TODO: Check API name and fix message
|
||||||
|
// TODO: Externalize string
|
||||||
|
await expect(apiMock.searchByTitle("Hooking Season Playtest")).resolves.toEqual(ret);
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
})
|
||||||
30
src/tests/ResponseMocks/MALMockMovie.json
Normal file
30
src/tests/ResponseMocks/MALMockMovie.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"mal_id": 2890,
|
||||||
|
"url": "https://myanimelist.net/anime/2890/Gake_no_Ue_no_Ponyo",
|
||||||
|
"title": "Gake no Ue no Ponyo",
|
||||||
|
"title_english": "Ponyo",
|
||||||
|
"type": "Movie",
|
||||||
|
"source": "Original",
|
||||||
|
"episodes": 1,
|
||||||
|
"aired": {
|
||||||
|
"from": "2008-07-19T00:00:00+00:00",
|
||||||
|
"to": null,
|
||||||
|
"prop": {
|
||||||
|
"from": {
|
||||||
|
"day": 19,
|
||||||
|
"month": 7,
|
||||||
|
"year": 2008
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"day": null,
|
||||||
|
"month": null,
|
||||||
|
"year": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"string": "Jul 19, 2008"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
src/tests/ResponseMocks/MusicBrainzMockResponse.json
Normal file
25
src/tests/ResponseMocks/MusicBrainzMockResponse.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"release-groups": [
|
||||||
|
{
|
||||||
|
"id": "9cf08bf9-1948-4087-abe1-783210ea1fae",
|
||||||
|
"primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc",
|
||||||
|
"title": "Halo Halo",
|
||||||
|
"first-release-date": "2013-07-08",
|
||||||
|
"primary-type": "Album",
|
||||||
|
"artist-credit": [
|
||||||
|
{
|
||||||
|
"name": "Halo Halo",
|
||||||
|
"artist": {
|
||||||
|
"name": "Halo Halo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"releases": [
|
||||||
|
{
|
||||||
|
"id": "58dd1d57-2201-472e-9e36-5d497dcedb6f",
|
||||||
|
"title": "Halo Halo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
src/tests/ResponseMocks/OMDBMockResponse.json
Normal file
13
src/tests/ResponseMocks/OMDBMockResponse.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"Search": [
|
||||||
|
{
|
||||||
|
"Title": "Guardians of the Galaxy",
|
||||||
|
"Year": "2014",
|
||||||
|
"imdbID": "tt2015381",
|
||||||
|
"Type": "movie",
|
||||||
|
"Poster": "https://m.media-amazon.com/images/M/MV5BMTAwMjU5OTgxNjZeQTJeQWpwZ15BbWU4MDUxNDYxODEx._V1_SX300.jpg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"totalResults": "1",
|
||||||
|
"Response": "True"
|
||||||
|
}
|
||||||
14
src/tests/ResponseMocks/SteamAPIMockResponse.json
Normal file
14
src/tests/ResponseMocks/SteamAPIMockResponse.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"applist": {
|
||||||
|
"apps": [
|
||||||
|
{
|
||||||
|
"appid": 2076590,
|
||||||
|
"name": "Hooking Season Playtest"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appid": 2076600,
|
||||||
|
"name": "MonsterTamer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/tests/ResponseMocks/WikipediaMockResponse.json
Normal file
20
src/tests/ResponseMocks/WikipediaMockResponse.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"query": {
|
||||||
|
"searchinfo": {
|
||||||
|
"totalhits": 1199001,
|
||||||
|
"suggestion": "book",
|
||||||
|
"suggestionsnippet": "book"
|
||||||
|
},
|
||||||
|
"search": [
|
||||||
|
{
|
||||||
|
"ns": 0,
|
||||||
|
"title": "Book",
|
||||||
|
"pageid": 3778,
|
||||||
|
"size": 68829,
|
||||||
|
"wordcount": 8821,
|
||||||
|
"snippet": "called <span class=\"searchmatch\">books</span> or chapters or parts, are parts. The intellectual content in a physical book need not be a composition, nor even be called a book. <span class=\"searchmatch\">Books</span> can",
|
||||||
|
"timestamp": "2022-08-19T19:13:56Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/tests/mockHelpers.ts
Normal file
87
src/tests/mockHelpers.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { GameModel } from "../models/GameModel";
|
||||||
|
import { MovieModel } from '../models/MovieModel';
|
||||||
|
import { MusicReleaseModel } from "../models/MusicReleaseModel";
|
||||||
|
import { WikiModel } from '../models/WikiModel';
|
||||||
|
import { MediaType } from "../utils/MediaType";
|
||||||
|
import { apiMock } from "./ParameterizedAPI.test";
|
||||||
|
import MALMockMovie from "./ResponseMocks/MALMockMovie.json";
|
||||||
|
import MusicBrainzResponseMock from "./ResponseMocks/MusicBrainzMockResponse.json";
|
||||||
|
import OMDBMockMovie from "./ResponseMocks/OMDBMockResponse.json";
|
||||||
|
import SteamAPIResponseMock from "./ResponseMocks/SteamAPIMockResponse.json";
|
||||||
|
import WikipediaMockResponse from "./ResponseMocks/WikipediaMockResponse.json";
|
||||||
|
|
||||||
|
export function setWikipediaResponseMock() {
|
||||||
|
let ret = [];
|
||||||
|
let wikiresponse = WikipediaMockResponse.query.search[0];
|
||||||
|
ret.push(new WikiModel({
|
||||||
|
type: 'wiki',
|
||||||
|
title: wikiresponse.title,
|
||||||
|
englishTitle: wikiresponse.title,
|
||||||
|
year: '',
|
||||||
|
dataSource: apiMock.apiName,
|
||||||
|
id: wikiresponse.pageid,
|
||||||
|
}));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setOMDbResponseMock() {
|
||||||
|
let ret = [];
|
||||||
|
let omdbresponse = OMDBMockMovie.Search[0];
|
||||||
|
ret.push(new MovieModel({
|
||||||
|
type: 'wiki',
|
||||||
|
title: omdbresponse.Title,
|
||||||
|
englishTitle: omdbresponse.Title,
|
||||||
|
year: omdbresponse.Year,
|
||||||
|
dataSource: apiMock.apiName,
|
||||||
|
id: omdbresponse.imdbID,
|
||||||
|
}));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMALResponseMock() {
|
||||||
|
let ret = [];
|
||||||
|
let result = MALMockMovie.data[0];
|
||||||
|
ret.push(new MovieModel({
|
||||||
|
type: result.type,
|
||||||
|
title: result.title,
|
||||||
|
englishTitle: result.title_english,
|
||||||
|
year: result.aired.prop.from.year,
|
||||||
|
dataSource: apiMock.apiName,
|
||||||
|
id: result.mal_id,
|
||||||
|
}));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSteamResponseMock() {
|
||||||
|
let ret = [];
|
||||||
|
let steamResponse = SteamAPIResponseMock.applist.apps[0];
|
||||||
|
ret.push(
|
||||||
|
new GameModel({
|
||||||
|
type: MediaType.Game,
|
||||||
|
title: steamResponse.name,
|
||||||
|
englishTitle: steamResponse.name,
|
||||||
|
year: '',
|
||||||
|
dataSource: apiMock.apiName,
|
||||||
|
id: steamResponse.appid,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMusicBrainzResponseMock() {
|
||||||
|
let ret = [];
|
||||||
|
let result = MusicBrainzResponseMock["release-groups"][0];
|
||||||
|
ret.push(new MusicReleaseModel({
|
||||||
|
type: 'musicRelease',
|
||||||
|
title: result.title,
|
||||||
|
englishTitle: result.title,
|
||||||
|
year: (new Date(result['first-release-date'])).getFullYear().toString(),
|
||||||
|
dataSource: apiMock.apiName,
|
||||||
|
url: '',
|
||||||
|
id: result.id,
|
||||||
|
|
||||||
|
artists: result['artist-credit'].map((a: any) => a.name),
|
||||||
|
subType: result['primary-type'],
|
||||||
|
} as MusicReleaseModel));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
24
src/tests/utils.test.ts
Normal file
24
src/tests/utils.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { wrapAround, containsOnlyLettersAndUnderscores, replaceIllegalFileNameCharactersInString} from '../utils/Utils';
|
||||||
|
|
||||||
|
test('If wrapAround wraps correctly', () => {
|
||||||
|
expect(wrapAround(100,5)).toBe(0);
|
||||||
|
expect(wrapAround(100,7)).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('If wrapAround errors out when dividing by zero', () => {
|
||||||
|
expect(wrapAround(100,0)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Letter and underscore string validity', () => {
|
||||||
|
expect(containsOnlyLettersAndUnderscores("asdkfj_")).toBe(true);
|
||||||
|
expect(containsOnlyLettersAndUnderscores("asdkfj0")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Letter and underscore unicode char test', () =>{
|
||||||
|
expect(containsOnlyLettersAndUnderscores("asdkaÈj")).toBe(true);
|
||||||
|
expect(containsOnlyLettersAndUnderscores("asdkaÈj0")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Valid filename test', ()=>{
|
||||||
|
expect(replaceIllegalFileNameCharactersInString("what?is\\this:")).toBe("whatisthis -");
|
||||||
|
})
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"DOM",
|
"DOM",
|
||||||
"ES5",
|
"ES5",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue