fix: ensure LoopContainer required fields are set via schema-driven normalization
This commit is contained in:
parent
0c64850b7d
commit
3dbb140131
|
|
@ -143,4 +143,64 @@ describe('ContainersCallService', () => {
|
|||
'Invalid container catalog response: expected reduced catalog format with a descriptors array'
|
||||
);
|
||||
});
|
||||
|
||||
it('fills missing required boolean fields using descriptor schema defaults', async () => {
|
||||
const typesRequest = firstValueFrom(service.retrieveAllContainerTypes());
|
||||
|
||||
httpMock.expectOne(`${environment.apiUrl}/containers/types/catalog`).flush({
|
||||
descriptors: [
|
||||
{
|
||||
type: 'LoopContainer',
|
||||
description: 'Loop container node',
|
||||
userInteractive: false,
|
||||
hasExampleContainer: true,
|
||||
exampleContainerEndpoint: '/containers/types/LoopContainer/example',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
maxIterations: { type: 'integer' },
|
||||
useLlm: { type: 'boolean' }
|
||||
},
|
||||
required: ['name', 'maxIterations', 'useLlm']
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await typesRequest;
|
||||
|
||||
const request = firstValueFrom(service.createContainer('container-1', {
|
||||
typeName: 'LoopContainer',
|
||||
specificConfiguration: {
|
||||
name: 'Loop',
|
||||
maxIterations: 3
|
||||
}
|
||||
}));
|
||||
|
||||
const createRequest = httpMock.expectOne(`${environment.apiUrl}/containers`);
|
||||
expect(createRequest.request.method).toBe('POST');
|
||||
expect(createRequest.request.body.useLlm).toBe(false);
|
||||
expect(createRequest.request.body.useLLM).toBeUndefined();
|
||||
|
||||
createRequest.flush({
|
||||
id: 'container-1',
|
||||
name: 'Loop',
|
||||
typeName: 'LoopContainer',
|
||||
specificConfiguration: {
|
||||
type: 'LoopContainerConfiguration',
|
||||
name: 'Loop',
|
||||
maxIterations: 3,
|
||||
useLlm: false
|
||||
},
|
||||
inputs: [],
|
||||
outputs: []
|
||||
});
|
||||
|
||||
await expect(request).resolves.toEqual(expect.objectContaining({
|
||||
id: 'container-1',
|
||||
typeName: 'LoopContainer',
|
||||
nodeFamily: 'container'
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -267,16 +267,48 @@ export class ContainersCallService extends ContainersCallServiceBase {
|
|||
|
||||
private buildContainerConfigurationPayload(containerType: string, configuration: Record<string, unknown>) {
|
||||
const { typeName: _ignoreTypeName, ...sanitized } = configuration;
|
||||
const configurationType = this.resolveConfigurationType(containerType, sanitized);
|
||||
const normalized = this.normalizeConfigurationWithSchema(containerType, sanitized);
|
||||
const configurationType = this.resolveConfigurationType(containerType, normalized);
|
||||
return {
|
||||
...sanitized,
|
||||
...normalized,
|
||||
type: configurationType,
|
||||
name: typeof sanitized["name"] === "string" && sanitized["name"].length > 0
|
||||
? sanitized["name"]
|
||||
name: typeof normalized["name"] === "string" && normalized["name"].length > 0
|
||||
? normalized["name"]
|
||||
: containerType
|
||||
};
|
||||
}
|
||||
|
||||
private normalizeConfigurationWithSchema(containerType: string, configuration: Record<string, unknown>) {
|
||||
const normalized = { ...configuration };
|
||||
const descriptor = this.containerTypesCache
|
||||
?.find((candidate) => candidate.type === containerType);
|
||||
const schema = descriptor?.schema;
|
||||
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const required = Array.isArray(schema["required"])
|
||||
? schema["required"].filter((value): value is string => typeof value === "string" && value.length > 0)
|
||||
: [];
|
||||
if (!required.length) return normalized;
|
||||
|
||||
const properties = this.toRecord(schema["properties"]);
|
||||
for (const key of required) {
|
||||
if (normalized[key] !== undefined) continue;
|
||||
const propertySchema = this.toRecord(properties[key]);
|
||||
const defaultValue = propertySchema["default"];
|
||||
if (typeof defaultValue === "boolean" || typeof defaultValue === "number" || typeof defaultValue === "string") {
|
||||
normalized[key] = defaultValue;
|
||||
continue;
|
||||
}
|
||||
if (propertySchema["type"] === "boolean") {
|
||||
normalized[key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private resolveConfigurationType(containerType: string, configuration: Record<string, unknown>) {
|
||||
const explicitType = this.toNullableString(configuration["type"]);
|
||||
if (explicitType) return explicitType;
|
||||
|
|
|
|||
|
|
@ -289,6 +289,7 @@ export async function addBlockToEditor(
|
|||
|
||||
const currentConfiguration = cloneValue(currentLiveNode.data.specificConfiguration ?? {}) as Record<string, unknown>;
|
||||
const nextConfiguration: Record<string, unknown> = {
|
||||
...currentConfiguration,
|
||||
name: String(currentConfiguration['name'] ?? currentLiveNode.data['name'] ?? 'Container'),
|
||||
subFlow: candidateSubFlow
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue