Makelore 2.0 initial clean snapshot
This commit is contained in:
149
tests/unit/game-asset-review.test.ts
Normal file
149
tests/unit/game-asset-review.test.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import {
|
||||
GameAssetReviewConflictError,
|
||||
loadGameAssetReview,
|
||||
recordGameAssetReviewActions,
|
||||
recordGameAssetReviewAction,
|
||||
} from '@electron/opencode/game-asset-review';
|
||||
|
||||
const temporaryDirectories: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe('game asset review state', () => {
|
||||
it('persists user decisions, resolves a review, and excludes terminal assets from later invocations', async () => {
|
||||
const projectPath = await mkdtemp(join(tmpdir(), 'niancode-asset-review-'));
|
||||
temporaryDirectories.push(projectPath);
|
||||
|
||||
const initial = await loadGameAssetReview(projectPath, 'review-1', ['hero', 'jump']);
|
||||
expect(initial).toMatchObject({ invocationId: 'review-1', status: 'pending', pendingAssetIds: ['hero', 'jump'] });
|
||||
|
||||
const approved = await recordGameAssetReviewAction(projectPath, {
|
||||
invocationId: 'review-1',
|
||||
candidateIds: ['hero', 'jump'],
|
||||
assetId: 'hero',
|
||||
action: 'approve',
|
||||
});
|
||||
expect(approved).toMatchObject({ pendingAssetIds: ['jump'], approvedAssetIds: ['hero'] });
|
||||
|
||||
const discarded = await recordGameAssetReviewAction(projectPath, {
|
||||
invocationId: 'review-1',
|
||||
candidateIds: ['hero', 'jump'],
|
||||
assetId: 'jump',
|
||||
action: 'replace',
|
||||
});
|
||||
expect(discarded).toMatchObject({ status: 'resolved', pendingAssetIds: [], discardedAssetIds: ['jump'] });
|
||||
|
||||
const next = await loadGameAssetReview(projectPath, 'review-2', ['hero', 'jump', 'coin']);
|
||||
expect(next).toMatchObject({ invocationId: 'review-2', candidateIds: ['hero', 'jump', 'coin'], pendingAssetIds: ['coin'] });
|
||||
|
||||
const state = JSON.parse(await readFile(join(projectPath, '.niancode', 'asset-review.json'), 'utf8')) as {
|
||||
invocations: Record<string, { candidateIds: string[] }>;
|
||||
};
|
||||
expect(state.invocations['review-2']?.candidateIds).toEqual(['hero', 'jump', 'coin']);
|
||||
});
|
||||
|
||||
it('makes repeated identical actions safe and rejects conflicting decisions', async () => {
|
||||
const projectPath = await mkdtemp(join(tmpdir(), 'niancode-asset-review-'));
|
||||
temporaryDirectories.push(projectPath);
|
||||
await loadGameAssetReview(projectPath, 'review-1', ['hero']);
|
||||
|
||||
const first = await recordGameAssetReviewAction(projectPath, {
|
||||
invocationId: 'review-1',
|
||||
candidateIds: ['hero'],
|
||||
assetId: 'hero',
|
||||
action: 'approve',
|
||||
});
|
||||
const repeated = await recordGameAssetReviewAction(projectPath, {
|
||||
invocationId: 'review-1',
|
||||
candidateIds: ['hero'],
|
||||
assetId: 'hero',
|
||||
action: 'approve',
|
||||
});
|
||||
expect(repeated).toEqual(first);
|
||||
|
||||
await expect(recordGameAssetReviewAction(projectPath, {
|
||||
invocationId: 'review-1',
|
||||
candidateIds: ['hero'],
|
||||
assetId: 'hero',
|
||||
action: 'discard',
|
||||
})).rejects.toBeInstanceOf(GameAssetReviewConflictError);
|
||||
});
|
||||
|
||||
it('applies a complete review batch atomically and keeps repeated batches idempotent', async () => {
|
||||
const projectPath = await mkdtemp(join(tmpdir(), 'niancode-asset-review-'));
|
||||
temporaryDirectories.push(projectPath);
|
||||
await loadGameAssetReview(projectPath, 'review-batch', ['hero', 'jump']);
|
||||
|
||||
const first = await recordGameAssetReviewActions(projectPath, {
|
||||
invocationId: 'review-batch',
|
||||
candidateIds: ['hero', 'jump'],
|
||||
decisions: [
|
||||
{ assetId: 'hero', action: 'approve' },
|
||||
{ assetId: 'jump', action: 'replace' },
|
||||
],
|
||||
});
|
||||
expect(first).toMatchObject({
|
||||
status: 'resolved',
|
||||
pendingAssetIds: [],
|
||||
approvedAssetIds: ['hero'],
|
||||
discardedAssetIds: ['jump'],
|
||||
decisions: { hero: 'approved', jump: 'replace-requested' },
|
||||
});
|
||||
|
||||
const repeated = await recordGameAssetReviewActions(projectPath, {
|
||||
invocationId: 'review-batch',
|
||||
candidateIds: ['hero', 'jump'],
|
||||
decisions: [
|
||||
{ assetId: 'hero', action: 'approve' },
|
||||
{ assetId: 'jump', action: 'replace' },
|
||||
],
|
||||
});
|
||||
expect(repeated).toEqual(first);
|
||||
|
||||
await expect(recordGameAssetReviewActions(projectPath, {
|
||||
invocationId: 'review-batch',
|
||||
candidateIds: ['hero', 'jump'],
|
||||
decisions: [
|
||||
{ assetId: 'hero', action: 'discard' },
|
||||
{ assetId: 'jump', action: 'approve' },
|
||||
],
|
||||
})).rejects.toBeInstanceOf(GameAssetReviewConflictError);
|
||||
expect(await loadGameAssetReview(projectPath, 'review-batch', ['hero', 'jump'])).toEqual(first);
|
||||
});
|
||||
|
||||
it('repairs an untouched empty invocation from the current Agent submission', async () => {
|
||||
const projectPath = await mkdtemp(join(tmpdir(), 'niancode-asset-review-'));
|
||||
temporaryDirectories.push(projectPath);
|
||||
|
||||
await loadGameAssetReview(projectPath, 'legacy-empty', []);
|
||||
const repaired = await loadGameAssetReview(projectPath, 'legacy-empty', ['hero', 'jump']);
|
||||
|
||||
expect(repaired).toMatchObject({
|
||||
invocationId: 'legacy-empty',
|
||||
candidateIds: ['hero', 'jump'],
|
||||
pendingAssetIds: ['hero', 'jump'],
|
||||
status: 'pending',
|
||||
});
|
||||
});
|
||||
|
||||
it('repairs an untouched invocation whose legacy ids no longer match the current plan', async () => {
|
||||
const projectPath = await mkdtemp(join(tmpdir(), 'niancode-asset-review-'));
|
||||
temporaryDirectories.push(projectPath);
|
||||
|
||||
await loadGameAssetReview(projectPath, 'legacy-unmatched', ['legacy-name']);
|
||||
const repaired = await loadGameAssetReview(projectPath, 'legacy-unmatched', ['hero']);
|
||||
|
||||
expect(repaired).toMatchObject({
|
||||
invocationId: 'legacy-unmatched',
|
||||
candidateIds: ['hero'],
|
||||
pendingAssetIds: ['hero'],
|
||||
status: 'pending',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user