108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import type { HarnessActorContext, HarnessSelection } from '@mosaicstack/types';
|
|
import { HarnessOperationError } from '../harness.registry.js';
|
|
import { FakeHarnessAdapter } from './fake-harness.adapter.js';
|
|
import { runHarnessAdapterContract } from './harness-adapter.contract.js';
|
|
|
|
const CONTEXT: HarnessActorContext = {
|
|
actorId: 'actor-1',
|
|
tenantId: 'tenant-1',
|
|
seatId: 'seat-1',
|
|
correlationId: 'correlation-1',
|
|
};
|
|
|
|
// The reusable conformance suite. Task 13 re-runs it against the native Pi adapter.
|
|
runHarnessAdapterContract('FakeHarnessAdapter', () => new FakeHarnessAdapter({ id: 'fake' }));
|
|
|
|
describe('FakeHarnessAdapter no-substitution', () => {
|
|
it('never substitutes the first catalog row when a bogus selection is requested', async () => {
|
|
const adapter = new FakeHarnessAdapter({ id: 'fake' });
|
|
const catalog = await adapter.catalog(CONTEXT);
|
|
const firstRow = catalog.models[0];
|
|
if (!firstRow) {
|
|
throw new Error('fixture requires a catalog model');
|
|
}
|
|
const available = catalog.models.find(
|
|
(entry) => entry.availability === 'available' && entry.modelId !== firstRow.modelId,
|
|
);
|
|
expect(available).toBeDefined();
|
|
const selected: HarnessSelection = {
|
|
harnessId: available!.harnessId,
|
|
providerId: available!.providerId,
|
|
modelId: available!.modelId,
|
|
};
|
|
|
|
const handle = await adapter.create({
|
|
context: CONTEXT,
|
|
conversationId: 'conversation-1',
|
|
selection: selected,
|
|
});
|
|
|
|
const bogus: HarnessSelection = {
|
|
harnessId: 'fake',
|
|
providerId: 'ghost-provider',
|
|
modelId: 'ghost-model',
|
|
};
|
|
|
|
let error: unknown;
|
|
try {
|
|
await handle.setModel(bogus);
|
|
} catch (caught) {
|
|
error = caught;
|
|
}
|
|
|
|
expect(error).toBeInstanceOf(HarnessOperationError);
|
|
const dto = (error as HarnessOperationError).dto;
|
|
expect(dto.code).toBe('selection_invalid');
|
|
// The DTO echoes the exact requested tuple, unchanged.
|
|
expect(dto.selection).toEqual(bogus);
|
|
// No substitution to the first catalog row.
|
|
expect(dto.selection).not.toEqual({
|
|
harnessId: firstRow.harnessId,
|
|
providerId: firstRow.providerId,
|
|
modelId: firstRow.modelId,
|
|
});
|
|
// The active selection is untouched by the rejected request.
|
|
expect((await handle.snapshot()).selection).toEqual(selected);
|
|
});
|
|
|
|
it('reports model_unavailable with the unchanged tuple for a known but unavailable model', async () => {
|
|
const adapter = new FakeHarnessAdapter({ id: 'fake' });
|
|
const catalog = await adapter.catalog(CONTEXT);
|
|
const unavailable = catalog.models.find((entry) => entry.availability === 'unavailable');
|
|
const available = catalog.models.find((entry) => entry.availability === 'available');
|
|
expect(unavailable).toBeDefined();
|
|
expect(available).toBeDefined();
|
|
|
|
const startingSelection: HarnessSelection = {
|
|
harnessId: available!.harnessId,
|
|
providerId: available!.providerId,
|
|
modelId: available!.modelId,
|
|
};
|
|
const handle = await adapter.create({
|
|
context: CONTEXT,
|
|
conversationId: 'conversation-2',
|
|
selection: startingSelection,
|
|
});
|
|
|
|
const requested: HarnessSelection = {
|
|
harnessId: unavailable!.harnessId,
|
|
providerId: unavailable!.providerId,
|
|
modelId: unavailable!.modelId,
|
|
};
|
|
|
|
let error: unknown;
|
|
try {
|
|
await handle.setModel(requested);
|
|
} catch (caught) {
|
|
error = caught;
|
|
}
|
|
|
|
expect(error).toBeInstanceOf(HarnessOperationError);
|
|
const dto = (error as HarnessOperationError).dto;
|
|
expect(dto.code).toBe('model_unavailable');
|
|
expect(dto.selection).toEqual(requested);
|
|
expect((await handle.snapshot()).selection).toEqual(startingSelection);
|
|
});
|
|
});
|