150 lines
4.8 KiB
TypeScript
150 lines
4.8 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 {
|
|
normalizeWorkflowInputBindings,
|
|
splitWorkflowInputBindings,
|
|
} from "../../../packages/contracts/src/workflow-input.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");
|
|
});
|
|
|
|
test("workflow input bindings normalize legacy asset and dataset inputs into one contract", () => {
|
|
const bindings = normalizeWorkflowInputBindings({
|
|
assetIds: ["asset-1", "asset-1", ""],
|
|
datasetIds: ["dataset-1", "dataset-2", "dataset-1"],
|
|
});
|
|
|
|
assert.deepEqual(bindings, [
|
|
{ kind: "asset", id: "asset-1" },
|
|
{ kind: "dataset", id: "dataset-1" },
|
|
{ kind: "dataset", id: "dataset-2" },
|
|
]);
|
|
assert.deepEqual(splitWorkflowInputBindings(bindings), {
|
|
assetIds: ["asset-1"],
|
|
datasetIds: ["dataset-1", "dataset-2"],
|
|
});
|
|
});
|
|
|
|
test("workflow input bindings prefer explicit bindings over legacy fallback arrays", () => {
|
|
const bindings = normalizeWorkflowInputBindings({
|
|
inputBindings: [
|
|
{ kind: "dataset", id: "dataset-9" },
|
|
{ kind: "dataset", id: "dataset-9" },
|
|
{ kind: "asset", id: "asset-2" },
|
|
],
|
|
assetIds: ["asset-legacy"],
|
|
});
|
|
|
|
assert.deepEqual(bindings, [
|
|
{ kind: "dataset", id: "dataset-9" },
|
|
{ kind: "asset", id: "asset-2" },
|
|
]);
|
|
});
|