Files
aryx/tests/main/gitRunChangeSummary.test.ts
T

269 lines
6.6 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import type { ProjectGitBaselineFile, ProjectGitWorkingTreeSnapshot } from '@shared/domain/project';
import { buildProjectGitRunChangeSummary } from '@main/git/gitRunChangeSummary';
const TIMESTAMP = '2026-03-31T00:00:00.000Z';
function createPreRunSnapshot(): ProjectGitWorkingTreeSnapshot {
return {
scannedAt: TIMESTAMP,
repoRoot: 'C:\\workspace\\alpha',
branch: 'main',
changedFileCount: 2,
changes: {
staged: 0,
unstaged: 1,
untracked: 1,
conflicted: 0,
},
files: [
{
path: 'src\\auth.ts',
unstagedStatus: 'modified',
},
{
path: 'legacy.tmp',
unstagedStatus: 'untracked',
},
],
};
}
function createPostRunSnapshot(): ProjectGitWorkingTreeSnapshot {
return {
scannedAt: TIMESTAMP,
repoRoot: 'C:\\workspace\\alpha',
branch: 'main',
changedFileCount: 2,
changes: {
staged: 0,
unstaged: 1,
untracked: 1,
conflicted: 0,
},
files: [
{
path: 'src\\auth.ts',
unstagedStatus: 'modified',
},
{
path: 'notes.txt',
unstagedStatus: 'untracked',
},
],
};
}
function createPreRunBaselines(): ProjectGitBaselineFile[] {
return [
{
path: 'src\\auth.ts',
combinedDiff: 'diff --git a/src/auth.ts b/src/auth.ts\n@@ -1 +1 @@\n-old\n+before\n',
},
{
path: 'legacy.tmp',
untrackedContentBase64: Buffer.from('legacy\n', 'utf8').toString('base64'),
},
];
}
function createPostRunBaselines(): ProjectGitBaselineFile[] {
return [
{
path: 'src\\auth.ts',
combinedDiff: 'diff --git a/src/auth.ts b/src/auth.ts\n@@ -1 +1 @@\n-old\n+after\n',
},
{
path: 'notes.txt',
untrackedContentBase64: Buffer.from('fresh notes\n', 'utf8').toString('base64'),
},
];
}
describe('buildProjectGitRunChangeSummary', () => {
test('classifies run-created, pre-existing, and cleaned files', () => {
const summary = buildProjectGitRunChangeSummary({
generatedAt: TIMESTAMP,
preRunSnapshot: createPreRunSnapshot(),
preRunBaselineFiles: createPreRunBaselines(),
postRunSnapshot: createPostRunSnapshot(),
postRunBaselineFiles: createPostRunBaselines(),
});
expect(summary).toMatchObject({
generatedAt: TIMESTAMP,
branchAtStart: 'main',
branchAtEnd: 'main',
fileCount: 3,
additions: 1,
deletions: 1,
counts: {
added: 0,
modified: 1,
deleted: 0,
renamed: 0,
copied: 0,
typeChanged: 0,
unmerged: 0,
untracked: 1,
cleaned: 1,
},
});
expect(summary?.files).toEqual([
{
path: 'notes.txt',
previousPath: undefined,
kind: 'untracked',
origin: 'run-created',
stagedStatus: undefined,
unstagedStatus: 'untracked',
additions: 0,
deletions: 0,
canRevert: true,
preview: {
path: 'notes.txt',
previousPath: undefined,
newFileContents: 'fresh notes\n',
},
},
{
path: 'legacy.tmp',
previousPath: undefined,
kind: 'cleaned',
origin: 'pre-existing',
stagedStatus: undefined,
unstagedStatus: 'untracked',
additions: 0,
deletions: 0,
canRevert: true,
preview: {
path: 'legacy.tmp',
previousPath: undefined,
newFileContents: 'legacy\n',
},
},
{
path: 'src\\auth.ts',
previousPath: undefined,
kind: 'modified',
origin: 'pre-existing',
stagedStatus: undefined,
unstagedStatus: 'modified',
additions: 1,
deletions: 1,
canRevert: true,
preview: {
path: 'src\\auth.ts',
previousPath: undefined,
diff: 'diff --git a/src/auth.ts b/src/auth.ts\n@@ -1 +1 @@\n-old\n+after\n',
},
},
]);
});
test('returns undefined when nothing changed across the run', () => {
const snapshot = createPreRunSnapshot();
const baselines = createPreRunBaselines();
expect(buildProjectGitRunChangeSummary({
generatedAt: TIMESTAMP,
preRunSnapshot: snapshot,
preRunBaselineFiles: baselines,
postRunSnapshot: snapshot,
postRunBaselineFiles: baselines,
})).toBeUndefined();
});
test('marks pre-existing files as non-revertable when the baseline capture has no restore data', () => {
const summary = buildProjectGitRunChangeSummary({
generatedAt: TIMESTAMP,
preRunSnapshot: createPreRunSnapshot(),
preRunBaselineFiles: [
{
path: 'src\\auth.ts',
},
],
postRunSnapshot: createPostRunSnapshot(),
postRunBaselineFiles: createPostRunBaselines(),
});
expect(summary?.files.find((file) => file.path === 'src\\auth.ts')).toMatchObject({
path: 'src\\auth.ts',
origin: 'pre-existing',
canRevert: false,
});
expect(summary?.files.find((file) => file.path === 'legacy.tmp')).toMatchObject({
path: 'legacy.tmp',
kind: 'cleaned',
canRevert: false,
});
});
test('preserves empty untracked file previews', () => {
const summary = buildProjectGitRunChangeSummary({
generatedAt: TIMESTAMP,
preRunSnapshot: {
scannedAt: TIMESTAMP,
repoRoot: 'C:\\workspace\\alpha',
branch: 'main',
changedFileCount: 1,
changes: {
staged: 0,
unstaged: 0,
untracked: 1,
conflicted: 0,
},
files: [
{
path: 'empty.txt',
unstagedStatus: 'untracked',
},
],
},
preRunBaselineFiles: [
{
path: 'empty.txt',
untrackedContentBase64: '',
},
],
postRunSnapshot: {
scannedAt: TIMESTAMP,
repoRoot: 'C:\\workspace\\alpha',
branch: 'main',
changedFileCount: 0,
changes: {
staged: 0,
unstaged: 0,
untracked: 0,
conflicted: 0,
},
files: [],
},
postRunBaselineFiles: [],
});
expect(summary?.files).toEqual([
{
path: 'empty.txt',
previousPath: undefined,
kind: 'cleaned',
origin: 'pre-existing',
stagedStatus: undefined,
unstagedStatus: 'untracked',
additions: 0,
deletions: 0,
canRevert: true,
preview: {
path: 'empty.txt',
previousPath: undefined,
newFileContents: '',
},
},
]);
});
});