humainflow-web/src/app/services/blocks/blocks-call.spec.ts

172 lines
5.8 KiB
TypeScript

import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { environment } from '@environment';
import { firstValueFrom } from 'rxjs';
import { BlocksCallService } from './blocks-call';
describe('BlocksCallService', () => {
let service: BlocksCallService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
BlocksCallService,
provideHttpClient(),
provideHttpClientTesting()
]
});
service = TestBed.inject(BlocksCallService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('retrieves block descriptors from the catalog endpoint and attaches shared definitions to each schema', async () => {
const request = firstValueFrom(service.retrieveAllBlocksTypes());
const catalogRequest = httpMock.expectOne(`${environment.apiUrl}/blocks/types/catalog`);
expect(catalogRequest.request.method).toBe('GET');
catalogRequest.flush({
sharedDefinitions: {
LLMDescriptor: {
type: 'object',
properties: {
provider: { type: 'string' },
model: { type: 'string' }
}
}
},
descriptors: [
{
type: 'LLMBlock',
description: 'LLM node',
userInteractive: false,
schema: {
type: 'object',
properties: {
llmDescriptor: {
$ref: '#/sharedDefinitions/LLMDescriptor'
}
}
}
}
]
});
const blockTypes = await request;
expect(blockTypes).toHaveLength(1);
expect(blockTypes[0]).toEqual(expect.objectContaining({
type: 'LLMBlock',
family: 'block',
description: 'LLM node'
}));
expect(blockTypes[0].schema).toEqual(expect.objectContaining({
type: 'object',
sharedDefinitions: {
LLMDescriptor: {
type: 'object',
properties: {
provider: { type: 'string' },
model: { type: 'string' }
}
}
},
properties: {
llmDescriptor: {
$ref: '#/sharedDefinitions/LLMDescriptor'
}
}
}));
});
it('rejects the legacy block catalog array format', async () => {
const request = firstValueFrom(service.retrieveAllBlocksTypes());
httpMock.expectOne(`${environment.apiUrl}/blocks/types/catalog`).flush([
{
type: 'LLMBlock'
}
]);
await expect(request).rejects.toThrow(
'Invalid block catalog response: expected reduced catalog format with a descriptors array'
);
});
it('loads and normalizes the dynamic bias annotations descriptor', async () => {
const request = firstValueFrom(service.retrieveBiasAnnotationsDescriptor());
const httpRequest = httpMock.expectOne(`${environment.apiUrl}/blocks/bias-annotations/descriptor`);
expect(httpRequest.request.method).toBe('GET');
httpRequest.flush({
type: 'BiasAnnotation', blockProperty: 'biasAnnotations', multiple: true, maxItems: 3,
schema: { type: 'object', required: ['category'], properties: { category: { type: 'string' } } },
options: { category: [{ value: 'DYNAMIC_VALUE', label: 'Dynamic label', description: 'Dynamic description' }] },
defaults: { status: 'DYNAMIC_DEFAULT' }, serverGeneratedFields: ['id']
});
await expect(request).resolves.toEqual(expect.objectContaining({
blockProperty: 'biasAnnotations', maxItems: 3,
options: { category: [{ value: 'DYNAMIC_VALUE', label: 'Dynamic label', description: 'Dynamic description' }] }
}));
});
it('retrieves and caches type-level bias capabilities', async () => {
const first = firstValueFrom(service.retrieveBiasCapabilities('LLMBlock'));
const request = httpMock.expectOne(`${environment.apiUrl}/blocks/types/LLMBlock/bias-capabilities`);
expect(request.request.method).toBe('GET');
request.flush({
blockType: 'LLMBlock',
supported: true,
isolatedExperimentSupported: true,
fullFlowExperimentSupported: true,
externalSideEffects: false,
configurationDependent: false,
activationModes: ['PROMPT_DIRECTIVE']
});
await expect(first).resolves.toEqual(expect.objectContaining({
blockType: 'LLMBlock', activationModes: ['PROMPT_DIRECTIVE']
}));
await expect(firstValueFrom(service.retrieveBiasCapabilities('LLMBlock'))).resolves.toEqual(expect.objectContaining({
supported: true
}));
});
it('posts the configured block for instance-specific bias capabilities', async () => {
const block = {
id: 'conditional-1',
name: 'Conditional',
inputs: [],
outputs: [{ name: 'true', type: 'TEXT', multiple: false }],
specificConfiguration: { useLlm: true },
typeName: 'ConditionalBlock',
nodeFamily: 'block' as const
};
const response = firstValueFrom(service.retrieveBiasCapabilitiesForInstance('ConditionalBlock', block));
const request = httpMock.expectOne(`${environment.apiUrl}/blocks/types/ConditionalBlock/bias-capabilities`);
expect(request.request.method).toBe('POST');
expect(request.request.body).toEqual(block);
request.flush({
blockType: 'ConditionalBlock',
supported: true,
isolatedExperimentSupported: true,
fullFlowExperimentSupported: true,
externalSideEffects: false,
configurationDependent: true,
activationModes: ['PROMPT_DIRECTIVE', 'INPUT_TRANSFORMATION']
});
await expect(response).resolves.toEqual(expect.objectContaining({
configurationDependent: true,
activationModes: ['PROMPT_DIRECTIVE', 'INPUT_TRANSFORMATION']
}));
});
});