Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -7,9 +7,22 @@ const content = Buffer.from([0x4d, 0x5a, 1, 2, 3, 4]);
|
||||
const sha256 = createHash('sha256').update(content).digest('hex');
|
||||
const url = 'https://github.com/alicealys/t6-gsc-utils/releases/tag/v3.1.0';
|
||||
const metadata = (overrides: Record<string, unknown> = {}) => ({
|
||||
id: 100, tag_name: 'v3.1.0', name: 'Utilities 3.1', body: 'Release notes <script>untrusted</script>',
|
||||
published_at: '2026-07-27T12:00:00Z', draft: false, prerelease: false,
|
||||
assets: [{ id: 200, name: 't6-gsc-utils.dll', size: content.length, digest: `sha256:${sha256}`, state: 'uploaded' }],
|
||||
id: 100,
|
||||
tag_name: 'v3.1.0',
|
||||
name: 'Utilities 3.1',
|
||||
body: 'Release notes <script>untrusted</script>',
|
||||
published_at: '2026-07-27T12:00:00Z',
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
assets: [
|
||||
{
|
||||
id: 200,
|
||||
name: 't6-gsc-utils.dll',
|
||||
size: content.length,
|
||||
digest: `sha256:${sha256}`,
|
||||
state: 'uploaded'
|
||||
}
|
||||
],
|
||||
...overrides
|
||||
});
|
||||
let fetchMock: ReturnType<typeof vi.fn>;
|
||||
@@ -18,38 +31,80 @@ beforeEach(async () => {
|
||||
fixture = await newFixture();
|
||||
|
||||
delete process.env.RCON_PASSWORD;
|
||||
fetchMock = vi.fn(async (input: string | URL) => String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(metadata()) : new Response(content));
|
||||
fetchMock = vi.fn(async (input: string | URL) =>
|
||||
String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(metadata())
|
||||
: new Response(content)
|
||||
);
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
});
|
||||
afterEach(async () => { vi.unstubAllGlobals(); await fixture.cleanup(); });
|
||||
afterEach(async () => {
|
||||
vi.unstubAllGlobals();
|
||||
await fixture.cleanup();
|
||||
});
|
||||
|
||||
describe('GitHub release lookup', () => {
|
||||
it('supports repository, latest, tag and the supplied release-anchor URLs', async () => {
|
||||
const { parseGithubReleaseUrl, getGithubRelease } = await import('../../src/lib/server/integrations/github-releases.js');
|
||||
expect(parseGithubReleaseUrl('https://github.com/alicealys/t6-gsc-utils/releases#release-v3.1.0')).toEqual({ repository: 'alicealys/t6-gsc-utils', tag: 'v3.1.0' });
|
||||
expect(parseGithubReleaseUrl('https://github.com/alicealys/t6-gsc-utils/releases/latest').tag).toBeNull();
|
||||
const { parseGithubReleaseUrl, getGithubRelease } =
|
||||
await import('../../src/lib/server/integrations/github-releases.js');
|
||||
expect(
|
||||
parseGithubReleaseUrl(
|
||||
'https://github.com/alicealys/t6-gsc-utils/releases#release-v3.1.0'
|
||||
)
|
||||
).toEqual({ repository: 'alicealys/t6-gsc-utils', tag: 'v3.1.0' });
|
||||
expect(
|
||||
parseGithubReleaseUrl('https://github.com/alicealys/t6-gsc-utils/releases/latest').tag
|
||||
).toBeNull();
|
||||
expect(parseGithubReleaseUrl('https://github.com/alicealys/t6-gsc-utils').tag).toBeNull();
|
||||
expect(parseGithubReleaseUrl('https://github.com/alicealys/t6-gsc-utils/releases/tag/release%2F3.1').tag).toBe('release/3.1');
|
||||
expect(
|
||||
parseGithubReleaseUrl(
|
||||
'https://github.com/alicealys/t6-gsc-utils/releases/tag/release%2F3.1'
|
||||
).tag
|
||||
).toBe('release/3.1');
|
||||
const release = await getGithubRelease(url);
|
||||
expect(release).toMatchObject({ repository: 'alicealys/t6-gsc-utils', tag: 'v3.1.0', assets: [{ name: 't6-gsc-utils.dll' }] });
|
||||
expect(fetchMock.mock.calls[0][0]).toBe('https://api.github.com/repos/alicealys/t6-gsc-utils/releases/tags/v3.1.0');
|
||||
expect(release).toMatchObject({
|
||||
repository: 'alicealys/t6-gsc-utils',
|
||||
tag: 'v3.1.0',
|
||||
assets: [{ name: 't6-gsc-utils.dll' }]
|
||||
});
|
||||
expect(fetchMock.mock.calls[0][0]).toBe(
|
||||
'https://api.github.com/repos/alicealys/t6-gsc-utils/releases/tags/v3.1.0'
|
||||
);
|
||||
});
|
||||
|
||||
it.each(['http://github.com/a/b', 'https://github.com.evil.test/a/b', 'https://github.com@127.0.0.1/a/b',
|
||||
'https://github.com:444/a/b', 'https://github.com/a/b/issues/1', 'https://github.com/a/b/releases#unknown',
|
||||
'https://github.com/a/b/releases/tag/', 'https://github.com/a/b/releases/tag/v1%0A'])('rejects unsupported URL %s without fetching it', async (input) => {
|
||||
const { getGithubRelease } = await import('../../src/lib/server/integrations/github-releases.js');
|
||||
it.each([
|
||||
'http://github.com/a/b',
|
||||
'https://github.com.evil.test/a/b',
|
||||
'https://github.com@127.0.0.1/a/b',
|
||||
'https://github.com:444/a/b',
|
||||
'https://github.com/a/b/issues/1',
|
||||
'https://github.com/a/b/releases#unknown',
|
||||
'https://github.com/a/b/releases/tag/',
|
||||
'https://github.com/a/b/releases/tag/v1%0A'
|
||||
])('rejects unsupported URL %s without fetching it', async (input) => {
|
||||
const { getGithubRelease } =
|
||||
await import('../../src/lib/server/integrations/github-releases.js');
|
||||
await expect(getGithubRelease(input)).rejects.toThrow();
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('filters out archives, unsafe filenames and oversized assets; shares concurrent lookups', async () => {
|
||||
const normal = metadata().assets[0];
|
||||
fetchMock.mockImplementation(async () => Response.json(metadata({ assets: [normal,
|
||||
{ ...normal, id: 201, name: 'source.zip' }, { ...normal, id: 202, name: '../evil.dll' },
|
||||
{ ...normal, id: 203, size: 65 * 1024 * 1024 }, { ...normal, id: 204, state: 'new' }] })));
|
||||
const { getGithubRelease } = await import('../../src/lib/server/integrations/github-releases.js');
|
||||
fetchMock.mockImplementation(async () =>
|
||||
Response.json(
|
||||
metadata({
|
||||
assets: [
|
||||
normal,
|
||||
{ ...normal, id: 201, name: 'source.zip' },
|
||||
{ ...normal, id: 202, name: '../evil.dll' },
|
||||
{ ...normal, id: 203, size: 65 * 1024 * 1024 },
|
||||
{ ...normal, id: 204, state: 'new' }
|
||||
]
|
||||
})
|
||||
)
|
||||
);
|
||||
const { getGithubRelease } =
|
||||
await import('../../src/lib/server/integrations/github-releases.js');
|
||||
const [first, second] = await Promise.all([getGithubRelease(url), getGithubRelease(url)]);
|
||||
expect(first.assets).toHaveLength(1);
|
||||
expect(second).toEqual(first);
|
||||
@@ -58,7 +113,8 @@ describe('GitHub release lookup', () => {
|
||||
|
||||
it('reports rate limits and retries failed lookups', async () => {
|
||||
fetchMock.mockResolvedValueOnce(new Response(null, { status: 403 }));
|
||||
const { getGithubRelease } = await import('../../src/lib/server/integrations/github-releases.js');
|
||||
const { getGithubRelease } =
|
||||
await import('../../src/lib/server/integrations/github-releases.js');
|
||||
await expect(getGithubRelease(url)).rejects.toMatchObject({ status: 429 });
|
||||
await expect(getGithubRelease(url)).resolves.toMatchObject({ tag: 'v3.1.0' });
|
||||
});
|
||||
@@ -66,17 +122,51 @@ describe('GitHub release lookup', () => {
|
||||
|
||||
describe('GitHub DLL imports', () => {
|
||||
it('captures provenance and notes, leaves promotion untouched, and deduplicates concurrent imports', async () => {
|
||||
const { importPluginRelease, pluginSource } = await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { getScript, getVersion, listVersions } = await import('../../src/lib/server/fleet/scripts.js');
|
||||
const imported = await importPluginRelease({ url, assetId: 200, name: 'Utilities', scopeType: 'game', scopeValue: 't6' });
|
||||
const { importPluginRelease, pluginSource } =
|
||||
await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { getScript, getVersion, listVersions } =
|
||||
await import('../../src/lib/server/fleet/scripts.js');
|
||||
const imported = await importPluginRelease({
|
||||
url,
|
||||
assetId: 200,
|
||||
name: 'Utilities',
|
||||
scopeType: 'game',
|
||||
scopeValue: 't6'
|
||||
});
|
||||
expect(imported.sha256).toBe(sha256);
|
||||
expect(imported.betaUrl).toBe(`/betas?pluginId=${imported.pluginId}&versionId=${imported.versionId}`);
|
||||
expect(getScript(imported.pluginId)).toMatchObject({ kind: 'plugin', target_rel_path: 't6-gsc-utils.dll', scope_value: 't6', current_version_id: null });
|
||||
expect(getVersion(imported.versionId)).toMatchObject({ release_version: 'v3.1.0', notes: metadata().body, state: 'untested' });
|
||||
expect((await import('../../src/lib/server/fleet/scripts.js')).getVersionContent(imported.versionId)).toEqual(content);
|
||||
expect(pluginSource(imported.pluginId)).toMatchObject({ repository: 'alicealys/t6-gsc-utils', release_id: 100, asset_id: 200, release_url: url });
|
||||
const duplicates = await Promise.all([1, 2].map(() => importPluginRelease({ url, assetId: 200, pluginId: imported.pluginId })));
|
||||
expect(duplicates.every(r => r.alreadyImported && r.versionId === imported.versionId)).toBe(true);
|
||||
expect(imported.betaUrl).toBe(
|
||||
`/betas?pluginId=${imported.pluginId}&versionId=${imported.versionId}`
|
||||
);
|
||||
expect(getScript(imported.pluginId)).toMatchObject({
|
||||
kind: 'plugin',
|
||||
target_rel_path: 't6-gsc-utils.dll',
|
||||
scope_value: 't6',
|
||||
current_version_id: null
|
||||
});
|
||||
expect(getVersion(imported.versionId)).toMatchObject({
|
||||
release_version: 'v3.1.0',
|
||||
notes: metadata().body,
|
||||
state: 'untested'
|
||||
});
|
||||
expect(
|
||||
(await import('../../src/lib/server/fleet/scripts.js')).getVersionContent(
|
||||
imported.versionId
|
||||
)
|
||||
).toEqual(content);
|
||||
expect(pluginSource(imported.pluginId)).toMatchObject({
|
||||
repository: 'alicealys/t6-gsc-utils',
|
||||
release_id: 100,
|
||||
asset_id: 200,
|
||||
release_url: url
|
||||
});
|
||||
const duplicates = await Promise.all(
|
||||
[1, 2].map(() =>
|
||||
importPluginRelease({ url, assetId: 200, pluginId: imported.pluginId })
|
||||
)
|
||||
);
|
||||
expect(
|
||||
duplicates.every((r) => r.alreadyImported && r.versionId === imported.versionId)
|
||||
).toBe(true);
|
||||
expect(listVersions(imported.pluginId)).toHaveLength(1);
|
||||
const { GET } = await import('../../src/routes/api/scripts/[id]/+server.js');
|
||||
const response = await (await GET({ params: { id: String(imported.pluginId) } })).json();
|
||||
@@ -86,100 +176,205 @@ describe('GitHub DLL imports', () => {
|
||||
|
||||
it('follows GitHub asset redirects and verifies the bytes', async () => {
|
||||
fetchMock.mockImplementation(async (input) => {
|
||||
if (String(input).startsWith('https://api.github.com/')) return Response.json(metadata());
|
||||
if (String(input).startsWith('https://github.com/')) return new Response(null, { status: 302, headers: { location: 'https://release-assets.githubusercontent.com/blob/test' } });
|
||||
if (String(input).startsWith('https://api.github.com/'))
|
||||
return Response.json(metadata());
|
||||
if (String(input).startsWith('https://github.com/'))
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { location: 'https://release-assets.githubusercontent.com/blob/test' }
|
||||
});
|
||||
return new Response(content);
|
||||
});
|
||||
const { importPluginRelease } = await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
await expect(importPluginRelease({ url, assetId: 200, name: 'Utilities' })).resolves.toMatchObject({ sha256 });
|
||||
const { importPluginRelease } =
|
||||
await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
await expect(
|
||||
importPluginRelease({ url, assetId: 200, name: 'Utilities' })
|
||||
).resolves.toMatchObject({ sha256 });
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it.each(['http://127.0.0.1/internal', 'https://evil.test/file.dll', 'https://user:pass@github.com/file.dll'])('blocks unsafe download redirects to %s', async (location) => {
|
||||
fetchMock.mockImplementation(async (input) => String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(metadata()) : new Response(null, { status: 302, headers: { location } }));
|
||||
const { importPluginRelease } = await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
it.each([
|
||||
'http://127.0.0.1/internal',
|
||||
'https://evil.test/file.dll',
|
||||
'https://user:pass@github.com/file.dll'
|
||||
])('blocks unsafe download redirects to %s', async (location) => {
|
||||
fetchMock.mockImplementation(async (input) =>
|
||||
String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(metadata())
|
||||
: new Response(null, { status: 302, headers: { location } })
|
||||
);
|
||||
const { importPluginRelease } =
|
||||
await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { listScripts } = await import('../../src/lib/server/fleet/scripts.js');
|
||||
await expect(importPluginRelease({ url, assetId: 200, name: 'Utilities' })).rejects.toThrow(/unsupported host/);
|
||||
await expect(importPluginRelease({ url, assetId: 200, name: 'Utilities' })).rejects.toThrow(
|
||||
/unsupported host/
|
||||
);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(listScripts()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('rejects checksum mismatches and truncated downloads without creating a plugin', async () => {
|
||||
const { importPluginRelease } = await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { importPluginRelease } =
|
||||
await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { listScripts } = await import('../../src/lib/server/fleet/scripts.js');
|
||||
fetchMock.mockImplementation(async (input) => String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(metadata()) : new Response(Buffer.alloc(content.length)));
|
||||
await expect(importPluginRelease({ url, assetId: 200, name: 'Utilities' })).rejects.toThrow(/checksum/);
|
||||
fetchMock.mockImplementation(async (input) => String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(metadata()) : new Response(Buffer.alloc(1)));
|
||||
await expect(importPluginRelease({ url, assetId: 200, name: 'Utilities' })).rejects.toThrow(/size differs/);
|
||||
fetchMock.mockImplementation(async (input) =>
|
||||
String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(metadata())
|
||||
: new Response(Buffer.alloc(content.length))
|
||||
);
|
||||
await expect(importPluginRelease({ url, assetId: 200, name: 'Utilities' })).rejects.toThrow(
|
||||
/checksum/
|
||||
);
|
||||
fetchMock.mockImplementation(async (input) =>
|
||||
String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(metadata())
|
||||
: new Response(Buffer.alloc(1))
|
||||
);
|
||||
await expect(importPluginRelease({ url, assetId: 200, name: 'Utilities' })).rejects.toThrow(
|
||||
/size differs/
|
||||
);
|
||||
expect(listScripts()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('rejects a missing asset and refuses to import into non-plugins or active betas', async () => {
|
||||
const { importPluginRelease } = await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { importPluginRelease } =
|
||||
await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { createScript } = await import('../../src/lib/server/fleet/scripts.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
await expect(importPluginRelease({ url, assetId: 999, name: 'Utilities' })).rejects.toThrow(/no longer available/);
|
||||
await expect(importPluginRelease({ url, assetId: 999, name: 'Utilities' })).rejects.toThrow(
|
||||
/no longer available/
|
||||
);
|
||||
const script = createScript({ name: 'Script', targetRelPath: 's.gsc', content });
|
||||
await expect(importPluginRelease({ url, assetId: 200, pluginId: script.script.id })).rejects.toMatchObject({ status: 404 });
|
||||
const plugin = createScript({ name: 'Plugin', kind: 'plugin', targetRelPath: 'p.dll', content });
|
||||
const beta = db().prepare("INSERT INTO betas (script_id, script_version_id, trigger_kind, state) VALUES (?, ?, 'version', 'running')")
|
||||
await expect(
|
||||
importPluginRelease({ url, assetId: 200, pluginId: script.script.id })
|
||||
).rejects.toMatchObject({ status: 404 });
|
||||
const plugin = createScript({
|
||||
name: 'Plugin',
|
||||
kind: 'plugin',
|
||||
targetRelPath: 'p.dll',
|
||||
content
|
||||
});
|
||||
const beta = db()
|
||||
.prepare(
|
||||
"INSERT INTO betas (script_id, script_version_id, trigger_kind, state) VALUES (?, ?, 'version', 'running')"
|
||||
)
|
||||
.run(plugin.script.id, plugin.version.id);
|
||||
db().prepare('INSERT INTO beta_scripts (beta_id, script_id, script_version_id, kind) VALUES (?, ?, ?, ?)')
|
||||
db()
|
||||
.prepare(
|
||||
'INSERT INTO beta_scripts (beta_id, script_id, script_version_id, kind) VALUES (?, ?, ?, ?)'
|
||||
)
|
||||
.run(beta.lastInsertRowid, plugin.script.id, plugin.version.id, 'plugin');
|
||||
await expect(importPluginRelease({ url, assetId: 200, pluginId: plugin.script.id })).rejects.toMatchObject({ status: 409 });
|
||||
await expect(
|
||||
importPluginRelease({ url, assetId: 200, pluginId: plugin.script.id })
|
||||
).rejects.toMatchObject({ status: 409 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('plugin update discovery', () => {
|
||||
it('finds newer stable releases, imports into the same plugin, and distinguishes imported from promoted', async () => {
|
||||
const { importPluginRelease, checkPluginUpdate } = await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { importPluginRelease, checkPluginUpdate } =
|
||||
await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { getScript, listVersions } = await import('../../src/lib/server/fleet/scripts.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const first = await importPluginRelease({ url, assetId: 200, name: 'Utilities' });
|
||||
const next = metadata({ id: 101, tag_name: 'v3.1.1', published_at: '2026-08-11T12:00:00Z',
|
||||
assets: [{ ...metadata().assets[0], id: 201 }] });
|
||||
fetchMock.mockImplementation(async (input) => String(input).startsWith('https://api.github.com/') ? Response.json(next) : new Response(content));
|
||||
const next = metadata({
|
||||
id: 101,
|
||||
tag_name: 'v3.1.1',
|
||||
published_at: '2026-08-11T12:00:00Z',
|
||||
assets: [{ ...metadata().assets[0], id: 201 }]
|
||||
});
|
||||
fetchMock.mockImplementation(async (input) =>
|
||||
String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(next)
|
||||
: new Response(content)
|
||||
);
|
||||
const update = await checkPluginUpdate(first.pluginId);
|
||||
expect(update).toMatchObject({ status: 'available', release: { tag: 'v3.1.1' } });
|
||||
const second = await importPluginRelease({ url: update.release!.url, assetId: 201, pluginId: first.pluginId });
|
||||
const second = await importPluginRelease({
|
||||
url: update.release!.url,
|
||||
assetId: 201,
|
||||
pluginId: first.pluginId
|
||||
});
|
||||
expect(listVersions(first.pluginId)).toHaveLength(2);
|
||||
expect(getScript(first.pluginId)!.current_version_id).toBeNull();
|
||||
expect(await checkPluginUpdate(first.pluginId)).toMatchObject({ status: 'imported', versionId: second.versionId });
|
||||
db().prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?').run(second.versionId, first.pluginId);
|
||||
expect(await checkPluginUpdate(first.pluginId)).toMatchObject({ status: 'current', versionId: second.versionId });
|
||||
expect(await checkPluginUpdate(first.pluginId)).toMatchObject({
|
||||
status: 'imported',
|
||||
versionId: second.versionId
|
||||
});
|
||||
db()
|
||||
.prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?')
|
||||
.run(second.versionId, first.pluginId);
|
||||
expect(await checkPluginUpdate(first.pluginId)).toMatchObject({
|
||||
status: 'current',
|
||||
versionId: second.versionId
|
||||
});
|
||||
});
|
||||
|
||||
it('does not recommend an older stable release after importing a newer prerelease', async () => {
|
||||
const { importPluginRelease, checkPluginUpdate } = await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { importPluginRelease, checkPluginUpdate } =
|
||||
await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const stable = await importPluginRelease({ url, assetId: 200, name: 'Utilities' });
|
||||
fetchMock.mockImplementation(async (input) => String(input).startsWith('https://api.github.com/') ? Response.json(metadata({ id: 105, tag_name: 'v4.0-rc1', prerelease: true, published_at: '2026-09-01T12:00:00Z' })) : new Response(content));
|
||||
const imported = await importPluginRelease({ url: url.replace('v3.1.0', 'v4.0-rc1'), assetId: 200, pluginId: stable.pluginId });
|
||||
fetchMock.mockImplementation(async (input) =>
|
||||
String(input).startsWith('https://api.github.com/')
|
||||
? Response.json(
|
||||
metadata({
|
||||
id: 105,
|
||||
tag_name: 'v4.0-rc1',
|
||||
prerelease: true,
|
||||
published_at: '2026-09-01T12:00:00Z'
|
||||
})
|
||||
)
|
||||
: new Response(content)
|
||||
);
|
||||
const imported = await importPluginRelease({
|
||||
url: url.replace('v3.1.0', 'v4.0-rc1'),
|
||||
assetId: 200,
|
||||
pluginId: stable.pluginId
|
||||
});
|
||||
fetchMock.mockImplementation(async () => Response.json(metadata()));
|
||||
expect(await checkPluginUpdate(imported.pluginId)).toMatchObject({ status: 'ahead' });
|
||||
});
|
||||
|
||||
it('reports untracked and GitHub failures per plugin without breaking the list', async () => {
|
||||
const { importPluginRelease } = await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { importPluginRelease } =
|
||||
await import('../../src/lib/server/fleet/plugin-releases.js');
|
||||
const { createScript } = await import('../../src/lib/server/fleet/scripts.js');
|
||||
await importPluginRelease({ url, assetId: 200, name: 'Imported' });
|
||||
createScript({ name: 'Manual', kind: 'plugin', targetRelPath: 'manual.dll', content });
|
||||
fetchMock.mockResolvedValue(new Response(null, { status: 404 }));
|
||||
const { GET } = await import('../../src/routes/api/plugins/updates/+server.js');
|
||||
const result = await (await GET({ url: new URL('http://localhost/api/plugins/updates') })).json();
|
||||
expect(result.updates.map((u: { status: string }) => u.status)).toEqual(['error', 'untracked']);
|
||||
const result = await (
|
||||
await GET({ url: new URL('http://localhost/api/plugins/updates') })
|
||||
).json();
|
||||
expect(result.updates.map((u: { status: string }) => u.status)).toEqual([
|
||||
'error',
|
||||
'untracked'
|
||||
]);
|
||||
});
|
||||
|
||||
it('serves the preview and import endpoints and validates bad requests', async () => {
|
||||
const { GET } = await import('../../src/routes/api/plugins/github/release/+server.js');
|
||||
const { POST } = await import('../../src/routes/api/plugins/github/import/+server.js');
|
||||
const preview = await GET({ url: new URL(`http://localhost/api/plugins/github/release?url=${encodeURIComponent(url)}`) });
|
||||
const preview = await GET({
|
||||
url: new URL(
|
||||
`http://localhost/api/plugins/github/release?url=${encodeURIComponent(url)}`
|
||||
)
|
||||
});
|
||||
expect((await preview.json()).release.assets[0].id).toBe(200);
|
||||
const request = (body: unknown) => new Request('http://localhost/api/plugins/github/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||
const request = (body: unknown) =>
|
||||
new Request('http://localhost/api/plugins/github/import', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
expect((await POST({ request: request(null) })).status).toBe(400);
|
||||
expect((await POST({ request: request({ url, assetId: 200, name: 'Test', scopeType: 'other' }) })).status).toBe(400);
|
||||
expect(
|
||||
(
|
||||
await POST({
|
||||
request: request({ url, assetId: 200, name: 'Test', scopeType: 'other' })
|
||||
})
|
||||
).status
|
||||
).toBe(400);
|
||||
const response = await POST({ request: request({ url, assetId: 200, name: 'Test' }) });
|
||||
expect(response.status).toBe(200);
|
||||
expect(await response.json()).toMatchObject({ ok: true, sha256, releaseVersion: 'v3.1.0' });
|
||||
|
||||
Reference in New Issue
Block a user