Use current promoted files for blueprint launches

This commit is contained in:
2026-09-19 19:58:30 -04:00
parent 41891b4209
commit 345839fbd4
15 changed files with 236 additions and 103 deletions
+52 -5
View File
@@ -56,6 +56,9 @@ describe('multi-server blueprints', () => {
const { previewCreation } = await import('../../src/lib/server/fleet/create.js');
expect(blueprint.totalServers).toBe(5);
expect(blueprint.entries.map(entry => entry.count)).toEqual([2, 1, 2]);
expect(blueprint).not.toHaveProperty('recipe');
const stored = db().prepare('SELECT recipe FROM server_blueprints WHERE id = ?').get(blueprint.id) as { recipe: string };
expect(JSON.parse(stored.recipe)).toEqual({ format: 3, entries });
expect(preview.ready).toBe(true);
expect(preview.servers.map(server => server.name)).toEqual(['t5_zombie_theater_1', 't5_zombie_theater_2', 't5_zombie_moon_1', 't6_zombie_town_1', 't6_zombie_town_2']);
expect(preview.servers.map(server => server.port)).toEqual([28960, 28961, 28962, 28963, 28964]);
@@ -66,21 +69,57 @@ describe('multi-server blueprints', () => {
expect(() => previewCreation({ blueprintId: blueprint.id })).toThrow(/multiple servers/);
});
it('upgrades an existing one-server blueprint and preserves its saved bytes when counts change', async () => {
it('reads a legacy one-server blueprint as a map selection and saves edits without snapshots', async () => {
const { captureRecipe, listBlueprints, updateBlueprint } = await import('../../src/lib/server/fleet/launch-recipes.js');
const { createScript, uploadNewVersion } = await import('../../src/lib/server/fleet/scripts.js');
const { previewCreation } = await import('../../src/lib/server/fleet/create.js');
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
const artifact = createScript({ name: 'Telemetry', targetRelPath: 'telemetry.gsc', scopeType: 'game', scopeValue: 't5', content: Buffer.from('legacy telemetry') });
db().prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?').run(artifact.version.id, artifact.script.id);
const recipe = captureRecipe('t5', 'theater');
db().prepare('INSERT INTO server_blueprints (id,name,created_at,recipe) VALUES (?, ?, ?, ?)').run('legacy', 'Old favorite', new Date().toISOString(), JSON.stringify(recipe));
const current = uploadNewVersion(artifact.script.id, Buffer.from('current telemetry'));
db().prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?').run(current.id, artifact.script.id);
closeDb();
const old = listBlueprints()[0];
expect(old.totalServers).toBe(1);
expect(old.entries).toEqual([{ count: 1, recipe: old.recipe }]);
expect(old.entries).toEqual([{ count: 1, game: 't5', mapSlug: 'theater', mapName: 'Kino Der Toten' }]);
expect(old).not.toHaveProperty('recipe');
expect(previewCreation({ blueprintId: 'legacy' }).recipe.files).toContainEqual(expect.objectContaining({ path: 'telemetry.gsc', versionId: current.id }));
const edited = updateBlueprint('legacy', { name: 'Whole lineup', entries });
expect(edited.totalServers).toBe(5);
expect(edited.entries[0].recipe.fingerprint).toBe(old.recipe.fingerprint);
expect(edited.entries[0]).toEqual({ ...old.entries[0], count: 2 });
const stored = db().prepare('SELECT recipe FROM server_blueprints WHERE id = ?').get('legacy') as { recipe: string };
expect(JSON.parse(stored.recipe)).toEqual({ format: 3, entries });
expect(listBlueprints()).toHaveLength(1);
});
it('uses current promotions for legacy collection blueprints while retaining counts and exact T6 variants', async () => {
const { captureRecipe, listBlueprints } = await import('../../src/lib/server/fleet/launch-recipes.js');
const { createScript, uploadNewVersion } = await import('../../src/lib/server/fleet/scripts.js');
const { previewCollection, queueCollection, getCollection } = await import('../../src/lib/server/fleet/collections.js');
const { processCreations } = await import('../../src/lib/server/fleet/create.js');
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
const artifact = createScript({ name: 'Legacy telemetry', targetRelPath: 'telemetry.gsc', scopeType: 'game', scopeValue: 't5', content: Buffer.from('legacy telemetry') });
db().prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?').run(artifact.version.id, artifact.script.id);
const legacyEntries = entries.map(({ count, game, mapSlug }) => ({ count, recipe: captureRecipe(game as 't5' | 't6', mapSlug) }));
db().prepare('INSERT INTO server_blueprints (id,name,created_at,recipe) VALUES (?, ?, ?, ?)').run('legacy-lineup', 'Legacy lineup', new Date().toISOString(), JSON.stringify({ format: 2, entries: legacyEntries }));
const current = uploadNewVersion(artifact.script.id, Buffer.from('current telemetry'));
db().prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?').run(current.id, artifact.script.id);
closeDb();
expect(listBlueprints()[0].entries.map(({ mapName: _mapName, ...entry }) => entry)).toEqual(entries);
const preview = previewCollection('legacy-lineup');
expect(preview.entries[0].recipe.files).toContainEqual(expect.objectContaining({ path: 'telemetry.gsc', versionId: current.id }));
expect(preview.entries[2].recipe.mapSlug).toBe('town');
const queued = queueCollection({ previewId: preview.id, keys: keysFor(preview) });
await processCreations();
expect(getCollection(queued.collectionId!)?.collection).toMatchObject({ total: 5, done: 5 });
for (const name of ['t5_zombie_theater_1', 't5_zombie_theater_2', 't5_zombie_moon_1']) {
expect(await readFile(join(fixture.t5, name, 'runtime/plutonium/storage/t5/raw/scripts/sp/telemetry.gsc'), 'utf8')).toBe('current telemetry');
}
expect(await readFile(join(fixture.t6, 't6_zombie_town_2', 'configs/electric_cherry_map.cfg'), 'utf8')).toContain('execgts zm_standard_town.cfg map zm_transit');
});
it.each([[], [{ game: 't5', mapSlug: 'theater', count: 0 }], [{ game: 't5', mapSlug: 'theater', count: 21 }],
[{ game: 't5', mapSlug: 'theater', count: 1.5 }], [{ game: 't5', mapSlug: 'theater', count: '2' }],
[{ game: 't5', mapSlug: 'town', count: 1 }], [entries[0], entries[0]]].map(input => ({ input })))('rejects an invalid lineup: $input', async ({ input }) => {
@@ -247,7 +286,7 @@ describe('atomic collection launches', () => {
expect(db().prepare('SELECT * FROM server_launch_collections').all()).toHaveLength(1);
});
it('keeps the reviewed lineup and bytes after blueprint edits, deletion, and promotion', async () => {
it('keeps the reviewed lineup after blueprint edits and deletion while using current promotions', async () => {
const { createScript, uploadNewVersion } = await import('../../src/lib/server/fleet/scripts.js');
const { db } = await import('../../src/lib/server/db/index.js');
const { script, version } = createScript({ name: 'Classic perks', kind: 'script', scopeType: 'game', scopeValue: 't5', targetRelPath: 'perks.gsc', content: Buffer.from('original') });
@@ -261,10 +300,18 @@ describe('atomic collection launches', () => {
updateBlueprint(blueprint.id, { name: 'Changed', entries: [{ ...entries[0], count: 1 }] });
deleteBlueprint(blueprint.id);
const queued = queueCollection({ previewId: preview.id, keys: keysFor(preview) });
const queuedDetail = getCollection(queued.collectionId!)!;
const { recipeForDeployment } = await import('../../src/lib/server/fleet/launch-recipes.js');
for (const server of queuedDetail.servers.filter(server => server.server.startsWith('t5_'))) {
expect(recipeForDeployment(server.id)?.recipe.files).toContainEqual(expect.objectContaining({ path: 'perks.gsc', versionId: newer.id }));
}
const newest = uploadNewVersion(script.id, Buffer.from('promoted while queued'));
db().prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?').run(newest.id, script.id);
uploadNewVersion(script.id, Buffer.from('unpromoted draft'));
await processCreations();
expect(getCollection(queued.collectionId!)?.collection).toMatchObject({ name: 'Friday Zombies', total: 5, done: 5 });
for (const name of ['t5_zombie_theater_1', 't5_zombie_theater_2', 't5_zombie_moon_1']) {
expect(await readFile(join(fixture.t5, name, 'runtime/plutonium/storage/t5/raw/scripts/sp/perks.gsc'), 'utf8')).toBe('original');
expect(await readFile(join(fixture.t5, name, 'runtime/plutonium/storage/t5/raw/scripts/sp/perks.gsc'), 'utf8')).toBe('promoted while queued');
}
});