|
| 1 | +import {setup} from '../../setup.mjs'; |
| 2 | + |
| 3 | +const appName = 'InstanceServiceListTransactionsTest'; |
| 4 | + |
| 5 | +setup({ |
| 6 | + neoConfig: { |
| 7 | + unitTestMode: true |
| 8 | + }, |
| 9 | + appConfig: { |
| 10 | + name : appName, |
| 11 | + isMounted : () => true, |
| 12 | + vnodeInitialising: false |
| 13 | + } |
| 14 | +}); |
| 15 | + |
| 16 | +import {test, expect} from '@playwright/test'; |
| 17 | +import Neo from '../../../../src/Neo.mjs'; |
| 18 | +import * as core from '../../../../src/core/_export.mjs'; |
| 19 | +import InstanceService from '../../../../src/ai/client/InstanceService.mjs'; |
| 20 | +import TransactionService from '../../../../src/ai/TransactionService.mjs'; |
| 21 | + |
| 22 | +// Neo.ai.client.InstanceService.listTransactions is the read-only `list_transactions` tool: a non-consuming |
| 23 | +// projection of the writer's undo state (TransactionService.stackOf) into a {committed, redo} audit summary |
| 24 | +// ({txId, status, opCount, labels} per tx). Pure read — no live tree, no handleRequest, no enforcement — so |
| 25 | +// these tests drive it against a real TransactionService with a minimal client stub. |
| 26 | + |
| 27 | +const ID = {agentId: 'agent-a', sessionId: 'sess-a'}; |
| 28 | + |
| 29 | +// a minimal valid reverse-op carrying a user-facing label |
| 30 | +const op = (label, seq) => ({ |
| 31 | + sequenceId : seq, |
| 32 | + originWriter : {agentId: 'agent-a', sessionId: 'sess-a'}, |
| 33 | + targetSubtreePath: ['root', 'leaf'], |
| 34 | + forward : {tool: 'set_instance_properties', args: {id: 'leaf', properties: {x: 1}}}, |
| 35 | + reverse : {tool: 'set_instance_properties', args: {id: 'leaf', properties: {x: 0}}}, |
| 36 | + label |
| 37 | +}); |
| 38 | + |
| 39 | +test.describe('Neo.ai.client.InstanceService — the `list_transactions` tool', () => { |
| 40 | + let service, transactionService; |
| 41 | + |
| 42 | + test.beforeEach(() => { |
| 43 | + transactionService = Neo.create(TransactionService); |
| 44 | + service = Neo.create(InstanceService, {client: {transactionService}}) |
| 45 | + }); |
| 46 | + |
| 47 | + const commit = (txId, label) => { |
| 48 | + transactionService.begin ({id: ID, txId}); |
| 49 | + transactionService.record({id: ID, txId, op: op(label, `${txId}:1`)}); |
| 50 | + return transactionService.commit({id: ID, txId}) |
| 51 | + }; |
| 52 | + |
| 53 | + test('projects the committed stack + the redo branch to an audit summary', async () => { |
| 54 | + commit('tx-1', 'set x on leaf'); |
| 55 | + commit('tx-2', 'set y on panel'); |
| 56 | + transactionService.undo({id: ID}); // tx-2 → undone, onto the redo branch |
| 57 | + |
| 58 | + const result = await service.listTransactions({}, ID); |
| 59 | + |
| 60 | + expect(result.committed.map(t => t.txId)).toEqual(['tx-1']); // tx-1 still undoable |
| 61 | + expect(result.committed[0]).toEqual({txId: 'tx-1', status: 'committed', opCount: 1, labels: ['set x on leaf']}); |
| 62 | + expect(result.redo.map(t => t.txId)).toEqual(['tx-2']); // tx-2 redoable |
| 63 | + expect(result.redo[0].status).toBe('undone'); |
| 64 | + expect(result.redo[0].labels).toEqual(['set y on panel']) |
| 65 | + }); |
| 66 | + |
| 67 | + test('read-only — multiple reads never mutate the stack', async () => { |
| 68 | + commit('tx-1', 'a'); |
| 69 | + |
| 70 | + await service.listTransactions({}, ID); |
| 71 | + await service.listTransactions({}, ID); |
| 72 | + |
| 73 | + expect(transactionService.stackOf({id: ID}).committed.map(t => t.txId)).toEqual(['tx-1']) // unchanged + still undoable |
| 74 | + }); |
| 75 | + |
| 76 | + test('fail-closed → empty lists for a no-writer-identity caller or an absent stack service', async () => { |
| 77 | + commit('tx-1', 'a'); |
| 78 | + |
| 79 | + expect(await service.listTransactions({}, null)).toEqual({committed: [], redo: []}); // no writer identity |
| 80 | + |
| 81 | + const bare = Neo.create(InstanceService, {client: {}}); |
| 82 | + expect(await bare.listTransactions({}, ID)).toEqual({committed: [], redo: []}) // no transactionService |
| 83 | + }); |
| 84 | +}); |
0 commit comments