mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-29 03:57:09 +00:00
test: harden coverage and browser release gates
This commit is contained in:
+1
-2
@@ -67,8 +67,7 @@ Useful focused checks from the repository root:
|
||||
node -c extension/background.js
|
||||
node -c extension/content.js
|
||||
node -c extension/popup.js
|
||||
node scripts/test-episode-utils.mjs
|
||||
node scripts/test-title-privacy.mjs
|
||||
npx vitest run extension/episode-utils.test.mjs extension/title-privacy.test.mjs
|
||||
node scripts/test-audio-settings.mjs
|
||||
node scripts/test-locales.cjs
|
||||
```
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { extractEpisodeId, sameEpisode } from './episode-utils.js';
|
||||
|
||||
describe('episode title matching', () => {
|
||||
it.each([
|
||||
['S01E01', 'S01E01'],
|
||||
['S1E1', 'S01E01'],
|
||||
['s01e01', 'S01E01'],
|
||||
['Season 1 Episode 2', 'S01E02'],
|
||||
['season 01 episode 02', 'S01E02'],
|
||||
['S01 - E01', 'S01E01'],
|
||||
['S01.E01', 'S01E01'],
|
||||
['S01/E01', 'S01E01'],
|
||||
['S01:E01', 'S01E01'],
|
||||
['S01,E01', 'S01E01'],
|
||||
['S01 E01', 'S01E01'],
|
||||
['Folge 5', 'EP005'],
|
||||
['Episode 12', 'EP012'],
|
||||
['Ep. 3', 'EP003'],
|
||||
['#42', 'EP042'],
|
||||
['S01E001', 'S01E001']
|
||||
])('extracts %s as %s', (title, expected) => {
|
||||
expect(extractEpisodeId(title)).toBe(expected);
|
||||
});
|
||||
|
||||
it.each([null, undefined, '', 123, 'Some Movie Title', 'Breaking Bad'])(
|
||||
'returns null for non-episode input %j',
|
||||
input => expect(extractEpisodeId(input)).toBeNull()
|
||||
);
|
||||
|
||||
it.each([
|
||||
['S01E01', 'S01E01'],
|
||||
['S01E01 - Pilot', 'S01E01'],
|
||||
['Folge 5', 'Episode 5'],
|
||||
['Episode 12', 'Ep. 12'],
|
||||
['#42', 'Folge 42'],
|
||||
[null, null],
|
||||
['', ''],
|
||||
['Some Movie', 'Some Movie']
|
||||
])('matches equivalent titles %j and %j', (left, right) => {
|
||||
expect(sameEpisode(left, right)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['S01E01', 'S01E02'],
|
||||
['S01E01', 'S02E01'],
|
||||
['Folge 1', 'Folge 2'],
|
||||
['Some Movie', 'Other Movie'],
|
||||
['S01E01', null],
|
||||
[null, 'Episode 5'],
|
||||
['S01E05', 'Episode 5'],
|
||||
['S01E01', 'EP001']
|
||||
])('rejects different titles %j and %j', (left, right) => {
|
||||
expect(sameEpisode(left, right)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,155 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
HOST_ACCESS_REQUIRED_STATUS,
|
||||
addTabHostAccessRequest,
|
||||
describeTabUrl,
|
||||
inspectTabHostAccess,
|
||||
isHostAccessError,
|
||||
normalizeTabId,
|
||||
removeTabHostAccessRequest,
|
||||
requestOriginPermission
|
||||
} from './host-access.js';
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
describe('host access helpers', () => {
|
||||
it('normalizes only positive safe tab IDs', () => {
|
||||
expect(HOST_ACCESS_REQUIRED_STATUS).toBe('host_permission_required');
|
||||
for (const invalid of [null, undefined, '', 0, true, [42], '42.5', Number.MAX_SAFE_INTEGER + 1]) {
|
||||
expect(normalizeTabId(invalid)).toBeNull();
|
||||
}
|
||||
expect(normalizeTabId('42')).toBe(42);
|
||||
expect(normalizeTabId(' 42 ')).toBe(42);
|
||||
});
|
||||
|
||||
it('describes supported origins with Firefox-compatible localhost permissions', () => {
|
||||
expect(describeTabUrl('https://emby.example:8443/web/index.html')).toEqual({
|
||||
url: 'https://emby.example:8443/web/index.html',
|
||||
host: 'emby.example:8443',
|
||||
originPattern: 'https://emby.example:8443/*'
|
||||
});
|
||||
expect(describeTabUrl('http://localhost:8096/web/', { includePort: false })).toEqual({
|
||||
url: 'http://localhost:8096/web/',
|
||||
host: 'localhost:8096',
|
||||
originPattern: 'http://localhost/*'
|
||||
});
|
||||
expect(describeTabUrl('chrome://extensions/')).toBeNull();
|
||||
expect(describeTabUrl('not a url')).toBeNull();
|
||||
});
|
||||
|
||||
it('checks the selected tab origin and preserves an unknown callback result', async () => {
|
||||
let containsRequest;
|
||||
const deniedChrome = {
|
||||
tabs: { get: async tabId => ({ id: tabId, url: 'https://video.example/watch' }) },
|
||||
permissions: {
|
||||
contains: async request => {
|
||||
containsRequest = request;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
await expect(inspectTabHostAccess(deniedChrome, 42)).resolves.toMatchObject({
|
||||
granted: false,
|
||||
host: 'video.example',
|
||||
originPattern: 'https://video.example/*'
|
||||
});
|
||||
expect(containsRequest).toEqual({ origins: ['https://video.example/*'] });
|
||||
|
||||
const unknownChrome = {
|
||||
runtime: {},
|
||||
tabs: { get: async tabId => ({ id: tabId, url: 'https://video.example/watch' }) },
|
||||
permissions: { contains: (_request, callback) => callback(undefined) }
|
||||
};
|
||||
await expect(inspectTabHostAccess(unknownChrome, 42)).resolves.toMatchObject({ granted: null });
|
||||
});
|
||||
|
||||
it('uses Firefox host patterns without ports', async () => {
|
||||
let containsRequest;
|
||||
const chromeApi = {
|
||||
runtime: { getBrowserInfo: async () => ({ name: 'Firefox' }) },
|
||||
tabs: {
|
||||
get: async tabId => ({
|
||||
id: tabId,
|
||||
url: 'http://localhost:8096/web/',
|
||||
pendingUrl: 'https://different.example/loading'
|
||||
})
|
||||
},
|
||||
permissions: {
|
||||
contains: async request => {
|
||||
containsRequest = request;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
await expect(inspectTabHostAccess(chromeApi, 42)).resolves.toMatchObject({
|
||||
host: 'localhost:8096',
|
||||
originPattern: 'http://localhost/*'
|
||||
});
|
||||
expect(containsRequest).toEqual({ origins: ['http://localhost/*'] });
|
||||
});
|
||||
|
||||
it('adds, removes, and requests permissions through promise and callback APIs', async () => {
|
||||
let added;
|
||||
expect(await addTabHostAccessRequest({
|
||||
permissions: { addHostAccessRequest: async request => { added = request; } }
|
||||
}, 42, 'https://video.example/*')).toBe(true);
|
||||
expect(added).toEqual({ tabId: 42, pattern: 'https://video.example/*' });
|
||||
expect(await addTabHostAccessRequest({ permissions: {} }, 42)).toBe(false);
|
||||
|
||||
let removed;
|
||||
expect(await removeTabHostAccessRequest({
|
||||
permissions: { removeHostAccessRequest: async request => { removed = request; } }
|
||||
}, 42, 'https://video.example/*')).toBe(true);
|
||||
expect(removed).toEqual({ tabId: 42, pattern: 'https://video.example/*' });
|
||||
expect(await removeTabHostAccessRequest({ permissions: {} }, 42)).toBe(false);
|
||||
|
||||
const callbackChrome = {
|
||||
runtime: {},
|
||||
permissions: { request: (_request, callback) => callback(true) }
|
||||
};
|
||||
await expect(requestOriginPermission(callbackChrome, 'https://video.example/*')).resolves.toBe(true);
|
||||
await expect(requestOriginPermission({ permissions: {} }, 'https://video.example/*')).resolves.toBeNull();
|
||||
expect(isHostAccessError(new Error('Missing host permission for the tab'))).toBe(true);
|
||||
expect(isHostAccessError(new Error('No tab with id: 42'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('host access recovery contracts', () => {
|
||||
it('keeps activation, permission recovery, and target identity guarded', () => {
|
||||
const background = fs.readFileSync(path.join(repoRoot, 'extension/background.js'), 'utf8');
|
||||
const popup = fs.readFileSync(path.join(repoRoot, 'extension/popup.js'), 'utf8');
|
||||
const popupHtml = fs.readFileSync(path.join(repoRoot, 'extension/popup.html'), 'utf8');
|
||||
const tabManager = fs.readFileSync(path.join(repoRoot, 'extension/modules/tab-manager.js'), 'utf8');
|
||||
|
||||
expect(background).toMatch(/await activateTargetTab\((?:message\.tabId|selectedTabId), message\.tabTitle\)/);
|
||||
expect(background).toMatch(/addTabHostAccessRequest\(chrome, tabId, access\.originPattern\)/);
|
||||
expect(background).toMatch(/retryPendingTarget\(\)/);
|
||||
expect(background).toMatch(/activationGeneration !== targetActivationGeneration/);
|
||||
expect(background).toMatch(/pendingTargetRequestId/);
|
||||
expect(background).toMatch(/addedOrigins\.includes\(pending\.originPattern\)/);
|
||||
expect(background).toMatch(/isCurrentTargetIdentity\(tabId, targetGeneration\)/);
|
||||
expect(background).toMatch(/message\.expectedTabId/);
|
||||
expect(background).toMatch(/completeForceSyncBeforeTargetChange\(selectedTabId\)/);
|
||||
expect(background).toMatch(/FORCE_SYNC_ACK'[\s\S]*ignored_unselected_tab/);
|
||||
expect(background).toMatch(/removeTabHostAccessRequest\([\s\S]*pendingTabId/);
|
||||
|
||||
const activationBody = background.slice(
|
||||
background.indexOf('async function activateTargetTab'),
|
||||
background.indexOf('async function retryPendingTarget')
|
||||
);
|
||||
expect(activationBody.indexOf('await injectContentScript')).toBeLessThan(
|
||||
activationBody.indexOf('currentTabId = selectedTabId')
|
||||
);
|
||||
expect(popup).toMatch(/response\?\.status === 'host_permission_required'/);
|
||||
expect(popup).toMatch(/requestOriginPermission\(chrome, requestedOriginPattern\)/);
|
||||
expect(popup).toMatch(/expectedCurrentTabId: tabId/);
|
||||
expect(popup).toMatch(/expectedTabId: tabId/);
|
||||
expect(tabManager).not.toMatch(/injectContentScript/);
|
||||
expect((background.match(/tabs\.onRemoved\.addListener/g) || []).length
|
||||
+ (tabManager.match(/tabs\.onRemoved\.addListener/g) || []).length).toBe(1);
|
||||
expect(popupHtml).toMatch(/id="siteAccessNotice"/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
TITLE_PRIVACY_MODES,
|
||||
applyTitlePrivacyToPayload,
|
||||
normalizeSendTabTitle,
|
||||
normalizeTabTitle,
|
||||
normalizeTitlePrivacyMode,
|
||||
sanitizeSharedTitle,
|
||||
sanitizeTabTitle
|
||||
} from './title-privacy.js';
|
||||
|
||||
describe('title privacy', () => {
|
||||
it('normalizes settings and tab notification prefixes', () => {
|
||||
expect(normalizeTitlePrivacyMode(undefined)).toBe(TITLE_PRIVACY_MODES.FULL);
|
||||
expect(normalizeTitlePrivacyMode('unknown')).toBe(TITLE_PRIVACY_MODES.FULL);
|
||||
expect(normalizeTitlePrivacyMode(TITLE_PRIVACY_MODES.HIDDEN)).toBe(TITLE_PRIVACY_MODES.HIDDEN);
|
||||
expect(normalizeSendTabTitle(undefined, TITLE_PRIVACY_MODES.FULL)).toBe(true);
|
||||
expect(normalizeSendTabTitle(undefined, TITLE_PRIVACY_MODES.EPISODE)).toBe(false);
|
||||
expect(normalizeSendTabTitle(true, TITLE_PRIVACY_MODES.HIDDEN)).toBe(true);
|
||||
expect(normalizeSendTabTitle(false, TITLE_PRIVACY_MODES.FULL)).toBe(false);
|
||||
expect(normalizeTabTitle('(12) Testvideo - YouTube')).toBe('Testvideo - YouTube');
|
||||
expect(normalizeTabTitle('[999+] Testvideo - YouTube')).toBe('Testvideo - YouTube');
|
||||
expect(normalizeTabTitle('(500) Days of Summer')).toBe('Days of Summer');
|
||||
expect(normalizeTabTitle(' ')).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps tab-title and media-title privacy independent', () => {
|
||||
expect(sanitizeTabTitle('(12) Private Tab', true)).toBe('Private Tab');
|
||||
expect(sanitizeTabTitle('Private Tab', false)).toBeNull();
|
||||
expect(sanitizeSharedTitle('Example Movie', 'full')).toBe('Example Movie');
|
||||
expect(sanitizeSharedTitle('Show Name - S01/E04 - Title', 'episode')).toBe('S01E04');
|
||||
expect(sanitizeSharedTitle('Folge 7 - Private Server', 'episode')).toBe('EP007');
|
||||
expect(sanitizeSharedTitle('Example Movie', 'episode')).toBeNull();
|
||||
expect(sanitizeSharedTitle('Show Name - S01E04', 'hidden')).toBeNull();
|
||||
});
|
||||
|
||||
it('rewrites only present media keys without mutating the input', () => {
|
||||
const input = {
|
||||
tabTitle: 'Private Tab',
|
||||
mediaTitle: 'Private Media',
|
||||
expectedTitle: 'S01E04',
|
||||
title: 'S01E04',
|
||||
currentTime: 42
|
||||
};
|
||||
expect(applyTitlePrivacyToPayload(input, 'hidden')).toEqual({
|
||||
tabTitle: 'Private Tab',
|
||||
mediaTitle: null,
|
||||
expectedTitle: null,
|
||||
title: null,
|
||||
currentTime: 42
|
||||
});
|
||||
expect(input.mediaTitle).toBe('Private Media');
|
||||
expect(applyTitlePrivacyToPayload({ tabTitle: 'Private Tab', status: 'heartbeat' }, 'episode')).toEqual({
|
||||
tabTitle: 'Private Tab',
|
||||
status: 'heartbeat'
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user