Simplify game files to automatic T5 and T6 uploads
This commit is contained in:
@@ -9,7 +9,7 @@ const state = vi.hoisted(() => ({ handle: null as Database.Database | null }));
|
||||
vi.mock('../../src/lib/server/db/index.js', () => ({ db: () => state.handle }));
|
||||
import {
|
||||
browserUploadStatus, createLibraryImport, createLibraryUpload, getHostLibraryCache,
|
||||
getLibraryVersion, listLibraries, publishBrowserUpload, queueLibraryDownload,
|
||||
getLibraryVersion, latestPublishedLibraries, listLibraries, newestReadyLibrary, publishBrowserUpload, queueLibraryDownload,
|
||||
requestBrowserPublication, requireReadyLibrary, updateHostLibraryCaches, uploadBrowserFile
|
||||
} from '../../src/lib/server/game-files/index.js';
|
||||
import { createEnrollment, createHost, enrollHost, listHosts } from '../../src/lib/server/hosts/index.js';
|
||||
@@ -24,8 +24,7 @@ const sha = (value: Uint8Array) => createHash('sha256').update(value).digest('he
|
||||
const file = (path = 'zone/base.ff', content = bytes) => ({ path, size: content.length, lastModified: modified });
|
||||
const upload = (id: string, path: string, content: Buffer, offset = 0, checksum = sha(content)) =>
|
||||
uploadBrowserFile(id, path, { offset, size: content.length, sha256: checksum }, new Response(new Uint8Array(content)).body);
|
||||
const begin = (game: 't5' | 't6' = 't6', files = [file()], version = '1') =>
|
||||
createLibraryUpload({ game, name: `Plutonium ${game.toUpperCase()}`, version, description: 'Uploaded game folder', files });
|
||||
const begin = (game: 't5' | 't6' = 't6', files = [file()]) => createLibraryUpload({ game, files });
|
||||
|
||||
beforeEach(async () => {
|
||||
directory = await mkdtemp(join(tmpdir(), 'ec-browser-library-'));
|
||||
@@ -37,6 +36,7 @@ afterEach(async () => {
|
||||
state.handle?.close(); state.handle = null;
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
vi.unstubAllEnvs();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
function host() {
|
||||
@@ -65,7 +65,7 @@ describe('browser folder uploads', () => {
|
||||
expect(ready.manifestSha256).toBe(manifestChecksum(ready.files));
|
||||
expect(await readFile(join(ready.storageLocation, file().path))).toEqual(bytes);
|
||||
expect(await browserUploadStatus(initial.id)).toMatchObject({ status: 'published' });
|
||||
expect(listLibraries()[0].versions[0]).not.toHaveProperty('dlc');
|
||||
expect(listLibraries().find(library => library.game === game)?.versions[0]).not.toHaveProperty('dlc');
|
||||
expect(listHosts()).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('browser folder uploads', () => {
|
||||
const chunkSize = (await browserUploadStatus(sizeProbe.id)).chunkSize;
|
||||
expect(chunkSize).toBeGreaterThan(0);
|
||||
const large = Buffer.alloc(chunkSize + 17, 0x41);
|
||||
const initial = begin('t6', [file('main/archive.iwd', large)], 'large');
|
||||
const initial = begin('t6', [file('main/archive.iwd', large)]);
|
||||
const first = large.subarray(0, chunkSize);
|
||||
await upload(initial.id, 'main/archive.iwd', first);
|
||||
expect((await browserUploadStatus(initial.id)).files[0]).toMatchObject({ receivedBytes: chunkSize, complete: false });
|
||||
@@ -146,17 +146,54 @@ describe('browser folder uploads', () => {
|
||||
expect((await browserUploadStatus(initial.id)).files[0].receivedBytes).toBe(0);
|
||||
});
|
||||
|
||||
it('keeps ready bytes and version metadata immutable and requires a new version label', async () => {
|
||||
it('keeps published files immutable while accepting repeated uploads without labels', async () => {
|
||||
const initial = begin(); await upload(initial.id, file().path, bytes);
|
||||
const ready = await publishBrowserUpload(initial.id);
|
||||
await expect(upload(initial.id, file().path, bytes)).rejects.toThrow();
|
||||
expect(() => state.handle!.prepare('UPDATE game_file_versions SET description=? WHERE id=?').run('changed', ready.id)).toThrow(/immutable/);
|
||||
expect(() => begin()).toThrow(/already exists/);
|
||||
expect((await publishBrowserUpload(initial.id)).manifestSha256).toBe(ready.manifestSha256);
|
||||
expect(begin('t6', [file()], '2').id).not.toBe(ready.id);
|
||||
const next = begin();
|
||||
expect(next.id).not.toBe(ready.id);
|
||||
expect(next.version).not.toBe(ready.version);
|
||||
expect(next.libraryId).toBe(ready.libraryId);
|
||||
expect(await readFile(join(ready.storageLocation, file().path))).toEqual(bytes);
|
||||
});
|
||||
|
||||
it('ignores legacy names, descriptions, and labels when accepting new game files', () => {
|
||||
const legacyFields = { name: 'same name', version: 'same version', description: 'a library description' };
|
||||
const first = createLibraryUpload({ game: 't5', files: [file()], ...legacyFields });
|
||||
const next = createLibraryUpload({ game: 't5', files: [file()], ...legacyFields });
|
||||
const otherGame = createLibraryUpload({ game: 't6', files: [file()], ...legacyFields });
|
||||
expect(first.description).toBe('');
|
||||
expect(first.version).not.toBe(legacyFields.version);
|
||||
expect(next.version).not.toBe(first.version);
|
||||
expect(next.libraryId).toBe(first.libraryId);
|
||||
expect(otherGame.libraryId).not.toBe(first.libraryId);
|
||||
expect(listLibraries().map(library => library.name)).toEqual(['Black Ops I (T5)', 'Black Ops II (T6)']);
|
||||
});
|
||||
|
||||
it('follows completion order and preserves the latest successful files during failed or unfinished uploads', async () => {
|
||||
vi.useFakeTimers({ toFake: ['Date'] });
|
||||
vi.setSystemTime(new Date('2026-01-01T00:00:00Z'));
|
||||
const slow = begin();
|
||||
vi.setSystemTime(new Date('2026-01-02T00:00:00Z'));
|
||||
const fast = begin();
|
||||
await upload(fast.id, file().path, bytes);
|
||||
vi.setSystemTime(new Date('2026-01-03T00:00:00Z'));
|
||||
await publishBrowserUpload(fast.id);
|
||||
expect(newestReadyLibrary('t6')?.id).toBe(fast.id);
|
||||
await upload(slow.id, file().path, bytes);
|
||||
vi.setSystemTime(new Date('2026-01-04T00:00:00Z'));
|
||||
await publishBrowserUpload(slow.id);
|
||||
const failed = begin();
|
||||
await requestBrowserPublication(failed.id);
|
||||
await vi.waitFor(() => expect(getLibraryVersion(failed.id)?.state).toBe('failed'));
|
||||
begin();
|
||||
expect(newestReadyLibrary('t6')?.id).toBe(slow.id);
|
||||
expect(latestPublishedLibraries().map(version => version.id)).toEqual([slow.id]);
|
||||
expect(listLibraries().find(library => library.game === 't6')?.latestVersionId).toBe(slow.id);
|
||||
});
|
||||
|
||||
it('recovers publication after the verified directory was renamed but the ready commit was interrupted', async () => {
|
||||
const initial = begin(); await upload(initial.id, file().path, bytes);
|
||||
const files = [{ path: file().path, size: bytes.length, sha256: sha(bytes) }];
|
||||
@@ -220,7 +257,7 @@ describe('browser folder uploads', () => {
|
||||
expect(getHostLibraryCache(target.id, ready.id)?.status).toBe('failed');
|
||||
updateHostLibraryCaches(target.id, [{ libraryVersionId: ready.id, status: 'ready', manifestSha256: ready.manifestSha256 }]);
|
||||
expect(requireReadyLibrary(target.id, ready.id).id).toBe(ready.id);
|
||||
const replacement = begin('t5', [file()], '2'); await upload(replacement.id, file().path, bytes);
|
||||
const replacement = begin('t5'); await upload(replacement.id, file().path, bytes);
|
||||
await publishBrowserUpload(replacement.id);
|
||||
expect(requireReadyLibrary(target.id, ready.id).manifestSha256).toBe(ready.manifestSha256);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user