EmboFlow/apps/api/test/domain-contracts.spec.ts

113 lines
3.6 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import {
ASSET_TYPES,
WORKFLOW_RUN_STATUSES,
WORKSPACE_TYPES,
} from "../../../packages/contracts/src/domain.ts";
import {
buildCustomNodeEnvelopePreview,
formatCustomNodeValidationIssue,
validateCustomNodeDefinition,
} from "../../../packages/contracts/src/custom-node.ts";
import { createMongoConnectionUri } from "../src/common/mongo/mongo.module.ts";
import {
ASSET_COLLECTION_NAME,
PROJECT_COLLECTION_NAME,
WORKFLOW_DEFINITION_COLLECTION_NAME,
WORKSPACE_COLLECTION_NAME,
} from "../src/common/mongo/schemas/index.ts";
test("workspace types include personal and team", () => {
assert.deepEqual(WORKSPACE_TYPES, ["personal", "team"]);
});
test("asset types include raw and dataset-oriented sources", () => {
assert.equal(ASSET_TYPES.includes("raw_file"), true);
assert.equal(ASSET_TYPES.includes("standard_dataset"), true);
assert.equal(ASSET_TYPES.includes("rosbag"), true);
});
test("workflow run statuses include the documented execution states", () => {
assert.equal(WORKFLOW_RUN_STATUSES.includes("pending"), true);
assert.equal(WORKFLOW_RUN_STATUSES.includes("running"), true);
assert.equal(WORKFLOW_RUN_STATUSES.includes("success"), true);
assert.equal(WORKFLOW_RUN_STATUSES.includes("failed"), true);
});
test("mongo connection uri builder uses environment-style config", () => {
assert.equal(
createMongoConnectionUri({
username: "emboflow",
password: "emboflow",
host: "localhost",
port: 27017,
database: "emboflow",
}),
"mongodb://emboflow:emboflow@localhost:27017/emboflow?authSource=admin",
);
});
test("schema collection names match the core domain objects", () => {
assert.equal(WORKSPACE_COLLECTION_NAME, "workspaces");
assert.equal(PROJECT_COLLECTION_NAME, "projects");
assert.equal(ASSET_COLLECTION_NAME, "assets");
assert.equal(WORKFLOW_DEFINITION_COLLECTION_NAME, "workflow_definitions");
});
test("custom node validation accepts a valid docker image utility node", () => {
const issues = validateCustomNodeDefinition({
name: "Merge Assets",
category: "Utility",
source: {
kind: "image",
image: "python:3.11-alpine",
command: ["python3", "-c", "print('merge')"],
},
contract: {
inputMode: "multi_asset_set",
outputMode: "asset_set",
artifactType: "json",
},
});
assert.deepEqual(issues, []);
});
test("custom node validation rejects invalid dockerfile and impossible source contract combinations", () => {
const issues = validateCustomNodeDefinition({
name: "Bad Source",
category: "Source",
source: {
kind: "dockerfile",
dockerfileContent: "CMD [\"python3\"]",
},
contract: {
inputMode: "multi_asset_set",
outputMode: "report",
artifactType: "json",
},
});
assert.deepEqual(issues, ["source_cannot_be_multi_input", "dockerfile_missing_from"]);
assert.equal(
formatCustomNodeValidationIssue("dockerfile_missing_from"),
"custom node dockerfile must include a FROM instruction",
);
});
test("custom node envelope preview reflects the declared input and output contract", () => {
const preview = buildCustomNodeEnvelopePreview({
inputMode: "multi_asset_set",
outputMode: "asset_set_with_report",
artifactType: "json",
});
assert.deepEqual(preview.input.context.assetIds, ["asset-123"]);
assert.deepEqual(preview.input.context.upstreamResults[0]?.result?.assetIds, ["asset-123"]);
assert.deepEqual(preview.output.result.assetIds, ["asset-123"]);
assert.equal(preview.output.result.artifactType, "json");
assert.equal(typeof preview.output.result.report, "object");
});