mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-04 19:21:33 -07:00
fix(anime): scope preferences, paginate sources, harden installs
- Preference store keys entries by extension package + bridge source id; legacy unscoped entries are discarded once instead of being handed to whichever extension asks first - Source picker's "Load more" appends the next page without duplicating streamed results - Repository index fetches and subtitle/APK downloads now time out and are size-bounded instead of hanging or growing unbounded - APK installs are staged to a temp file and renamed into place - Stream metadata lookup matches the requested path, not only the currently playing one - Reworked animeui into browse-state/detail-panel/panels.css modules - Reverted premature CHANGELOG unreleased entries; refreshed anime-browser docs
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtemp, readFile, readdir, writeFile } from 'node:fs/promises';
|
||||
import { chmod, mkdtemp, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { PreferenceStore } from './preference-store';
|
||||
@@ -12,9 +12,49 @@ async function storeFile(): Promise<string> {
|
||||
|
||||
test('values round-trip through the file', async () => {
|
||||
const file = await storeFile();
|
||||
await new PreferenceStore(file).set('src-1', [{ key: 'address' }]);
|
||||
await new PreferenceStore(file).set('pkg', 'src-1', [{ key: 'address' }]);
|
||||
|
||||
assert.deepEqual(await new PreferenceStore(file).get('src-1'), [{ key: 'address' }]);
|
||||
assert.deepEqual(await new PreferenceStore(file).get('pkg', 'src-1'), [{ key: 'address' }]);
|
||||
});
|
||||
|
||||
test('the same bridge source id is isolated between extension packages', async () => {
|
||||
const file = await storeFile();
|
||||
const store = new PreferenceStore(file);
|
||||
|
||||
await store.set('pkg.one', 'shared-source', [{ key: 'password', value: 'one-secret' }]);
|
||||
await store.set('pkg.two', 'shared-source', [{ key: 'password', value: 'two-secret' }]);
|
||||
|
||||
const reloaded = new PreferenceStore(file);
|
||||
assert.deepEqual(await reloaded.get('pkg.one', 'shared-source'), [
|
||||
{ key: 'password', value: 'one-secret' },
|
||||
]);
|
||||
assert.deepEqual(await reloaded.get('pkg.two', 'shared-source'), [
|
||||
{ key: 'password', value: 'two-secret' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('a legacy bare source id is discarded rather than assigned to an unproven package', async () => {
|
||||
const file = await storeFile();
|
||||
await writeFile(file, JSON.stringify({ 'legacy-source': [{ key: 'address', value: 'saved' }] }));
|
||||
|
||||
const store = new PreferenceStore(file);
|
||||
assert.deepEqual(await store.get('pkg.one', 'legacy-source'), []);
|
||||
|
||||
const persisted = JSON.parse(await readFile(file, 'utf8')) as Record<string, unknown>;
|
||||
assert.equal(persisted['legacy-source'], undefined);
|
||||
assert.equal(persisted['pkg.one:legacy-source'], undefined);
|
||||
});
|
||||
|
||||
test('an ambiguous legacy source id is discarded instead of exposed to either package', async () => {
|
||||
const file = await storeFile();
|
||||
await writeFile(file, JSON.stringify({ shared: [{ key: 'password', value: 'old-secret' }] }));
|
||||
|
||||
const store = new PreferenceStore(file);
|
||||
assert.deepEqual(await store.get('pkg.one', 'shared'), []);
|
||||
assert.deepEqual(await store.get('pkg.two', 'shared'), []);
|
||||
|
||||
const persisted = JSON.parse(await readFile(file, 'utf8')) as Record<string, unknown>;
|
||||
assert.equal(persisted.shared, undefined);
|
||||
});
|
||||
|
||||
test('concurrent writes on a cold cache do not lose an update', async () => {
|
||||
@@ -23,30 +63,34 @@ test('concurrent writes on a cold cache do not lose an update', async () => {
|
||||
|
||||
// Both start before either has loaded; unserialized they would each get their
|
||||
// own object and the later persist would drop the other's entry.
|
||||
await Promise.all([store.set('src-1', [{ key: 'a' }]), store.set('src-2', [{ key: 'b' }])]);
|
||||
await Promise.all([
|
||||
store.set('pkg', 'src-1', [{ key: 'a' }]),
|
||||
store.set('pkg', 'src-2', [{ key: 'b' }]),
|
||||
]);
|
||||
|
||||
const reloaded = new PreferenceStore(file);
|
||||
assert.deepEqual(await reloaded.get('src-1'), [{ key: 'a' }]);
|
||||
assert.deepEqual(await reloaded.get('src-2'), [{ key: 'b' }]);
|
||||
assert.deepEqual(await reloaded.get('pkg', 'src-1'), [{ key: 'a' }]);
|
||||
assert.deepEqual(await reloaded.get('pkg', 'src-2'), [{ key: 'b' }]);
|
||||
});
|
||||
|
||||
test('a clear racing a set is applied in order', async () => {
|
||||
const file = await storeFile();
|
||||
const store = new PreferenceStore(file);
|
||||
await store.set('pkg:src', [{ key: 'password' }]);
|
||||
await store.set('pkg', 'src', [{ key: 'password' }]);
|
||||
|
||||
await Promise.all([store.clear('pkg'), store.set('other:src', [{ key: 'x' }])]);
|
||||
await Promise.all([store.clear('pkg'), store.set('other', 'src', [{ key: 'x' }])]);
|
||||
|
||||
const reloaded = new PreferenceStore(file);
|
||||
assert.deepEqual(await reloaded.get('pkg:src'), []);
|
||||
assert.deepEqual(await reloaded.get('other:src'), [{ key: 'x' }]);
|
||||
assert.deepEqual(await reloaded.get('pkg', 'src'), []);
|
||||
assert.deepEqual(await reloaded.get('other', 'src'), [{ key: 'x' }]);
|
||||
});
|
||||
|
||||
test('the file is written owner-only and leaves no temporary behind', async () => {
|
||||
test('the file is written owner-only even when an existing temporary file is permissive', async () => {
|
||||
const file = await storeFile();
|
||||
await new PreferenceStore(file).set('src-1', [{ key: 'password' }]);
|
||||
await writeFile(`${file}.tmp`, 'stale');
|
||||
await chmod(`${file}.tmp`, 0o666);
|
||||
await new PreferenceStore(file).set('pkg', 'src-1', [{ key: 'password' }]);
|
||||
|
||||
const { stat } = await import('node:fs/promises');
|
||||
assert.equal((await stat(file)).mode & 0o777, 0o600);
|
||||
assert.deepEqual(await readdir(path.dirname(file)), [path.basename(file)]);
|
||||
});
|
||||
@@ -55,15 +99,30 @@ test('a corrupt file starts empty rather than blocking the browser', async () =>
|
||||
const file = await storeFile();
|
||||
await writeFile(file, '{ not json');
|
||||
|
||||
assert.deepEqual(await new PreferenceStore(file).get('src-1'), []);
|
||||
assert.deepEqual(await new PreferenceStore(file).get('pkg', 'src-1'), []);
|
||||
});
|
||||
|
||||
test('malformed persisted values are filtered to preference objects with string keys', async () => {
|
||||
const file = await storeFile();
|
||||
await writeFile(
|
||||
file,
|
||||
JSON.stringify({
|
||||
'pkg:source': [null, { key: 42 }, 'bad', { key: 'valid', value: 'kept' }],
|
||||
'pkg:not-an-array': { key: 'invalid-container' },
|
||||
}),
|
||||
);
|
||||
|
||||
const store = new PreferenceStore(file);
|
||||
assert.deepEqual(await store.get('pkg', 'source'), [{ key: 'valid', value: 'kept' }]);
|
||||
assert.deepEqual(await store.get('pkg', 'not-an-array'), []);
|
||||
});
|
||||
|
||||
test('a write replaces the previous contents wholesale', async () => {
|
||||
const file = await storeFile();
|
||||
const store = new PreferenceStore(file);
|
||||
await store.set('src-1', [{ key: 'first' }]);
|
||||
await store.set('src-1', [{ key: 'second' }]);
|
||||
await store.set('pkg', 'src-1', [{ key: 'first' }]);
|
||||
await store.set('pkg', 'src-1', [{ key: 'second' }]);
|
||||
|
||||
const parsed = JSON.parse(await readFile(file, 'utf8')) as Record<string, unknown[]>;
|
||||
assert.deepEqual(parsed['src-1'], [{ key: 'second' }]);
|
||||
assert.deepEqual(parsed['pkg:src-1'], [{ key: 'second' }]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user