blob: 9bc6c4d4e2eca563871c0adcb0649e0e6060aeb1 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import assert from 'node:assert/strict';
import test from 'node:test';
import type { RuntimeHostConnection } from '@maka/runtime-host/client';
import type { SessionCatalogProjection, SessionUpdateResult } from '@maka/runtime-host/protocol';
import {
RuntimeHostSessionUpdateError,
getRuntimeHostSession,
updateRuntimeHostSession,
} from '../runtime-host-session-update.js';
test('reads the latest Session and returns the committed update', async () => {
const requests: unknown[] = [];
const connection = fakeConnection(async (_operation, input) => {
requests.push(input);
return { kind: 'session', session: sessionProjection({ revision: 7 }) };
});
const committed = await updateRuntimeHostSession(
connection,
'session-1',
async (current) => {
assert.equal(current.revision, 7);
return {
kind: 'committed',
session: sessionProjection({ revision: 8, permissionMode: 'bypass' }),
};
},
{ operation: 'session.configuration.update' },
);
assert.equal(committed.revision, 8);
assert.equal(committed.permissionMode, 'bypass');
assert.deepEqual(requests, [{ kind: 'get', sessionId: 'session-1' }]);
});
test('rereads after a revision conflict and stops after three attempts', async () => {
let reads = 0;
let updates = 0;
const connection = fakeConnection(async () => {
reads += 1;
return { kind: 'session', session: sessionProjection({ revision: reads }) };
});
await assert.rejects(
updateRuntimeHostSession(
connection,
'session-1',
async () => {
updates += 1;
return {
kind: 'revision_conflict',
expectedRevision: updates,
actualRevision: updates + 1,
};
},
{ operation: 'session.configuration.update' },
),
(error: unknown) => {
assert.ok(error instanceof RuntimeHostSessionUpdateError);
assert.equal(error.operation, 'session.configuration.update');
assert.equal(error.reason, 'revision_conflict');
assert.equal(error.attempts, 3);
return true;
},
);
assert.equal(reads, 3);
assert.equal(updates, 3);
});
test('reports missing and unsupported Session projections without caller-specific errors', async () => {
const missing = fakeConnection(async () => ({ kind: 'session', session: null }));
await assert.rejects(
updateRuntimeHostSession(
missing,
'session-1',
async (): Promise<SessionUpdateResult> => {
assert.fail('missing Session must not reach the update');
},
{ operation: 'session.configuration.update' },
),
(error: unknown) => {
assert.ok(error instanceof RuntimeHostSessionUpdateError);
assert.equal(error.operation, 'session.catalog.query');
assert.equal(error.reason, 'not_found');
assert.equal(
error.message,
'Runtime Host Session update failed for session session-1: not_found',
);
return true;
},
);
for (const [name, result, reason] of [
[
'legacy',
{
kind: 'session',
session: {
kind: 'unsupported_legacy_record',
id: 'session-1',
revision: 1,
reason: 'not_wire_representable',
},
},
'unsupported_session_projection',
],
['invalid', { kind: 'page', sessions: [], nextCursor: null }, 'invalid_projection'],
] as const) {
const connection = fakeConnection(async () => result);
await assert.rejects(getRuntimeHostSession(connection, 'session-1'), (error: unknown) => {
assert.ok(error instanceof RuntimeHostSessionUpdateError, name);
assert.equal(error.operation, 'session.catalog.query');
assert.equal(error.reason, reason);
return true;
});
}
});
test('checks caller lifecycle before every read and update', async () => {
let allowed = true;
let checks = 0;
let reads = 0;
const connection = fakeConnection(async () => {
reads += 1;
return { kind: 'session', session: sessionProjection() };
});
await assert.rejects(
updateRuntimeHostSession(
connection,
'session-1',
async (): Promise<SessionUpdateResult> => {
assert.fail('update must not start after the lifecycle closes');
},
{
operation: 'session.configuration.update',
assertRequestAllowed: () => {
checks += 1;
if (!allowed) throw new Error('closed');
allowed = false;
},
},
),
/closed/,
);
assert.equal(checks, 2);
assert.equal(reads, 1);
});
function fakeConnection(
request: (operation: string, input: unknown) => Promise<unknown>,
): Pick<RuntimeHostConnection, 'request'> {
return { request } as Pick<RuntimeHostConnection, 'request'>;
}
function sessionProjection(
overrides: Partial<SessionCatalogProjection> = {},
): SessionCatalogProjection {
return {
id: 'session-1',
revision: 1,
workspace: { target: { kind: 'host_path', path: '/workspace' }, hostCwd: '/workspace' },
createdAt: 1,
activityAt: 1,
name: 'Session',
isFlagged: false,
isArchived: false,
labels: [],
labelsTruncated: false,
hasUnread: false,
status: 'active',
backend: 'ai-sdk',
llmConnectionId: 'connection-1',
llmConnectionSlug: 'default',
connectionLocked: false,
model: 'default',
permissionMode: 'ask',
collaborationMode: 'agent',
orchestrationMode: 'default',
...overrides,
};
}