mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
refactor: check type contracts without runtime test startup (#155099)
* test(plugin-sdk): move type checks into compiler contracts * test: complete compiler contract migration * fix: update keyed-store contract evidence path
This commit is contained in:
parent
e331b97066
commit
71e7cf3b23
13 changed files with 178 additions and 189 deletions
|
|
@ -1,24 +0,0 @@
|
|||
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||
import type { WorkboardBoardMetadata, WorkboardBoardSummary } from "./index.js";
|
||||
|
||||
describe("workboard board automation contract", () => {
|
||||
it("carries the owning automation job reference in metadata and summaries", () => {
|
||||
const metadata: WorkboardBoardMetadata = {
|
||||
id: "planning",
|
||||
automationJobId: "job-categorize-planning",
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
};
|
||||
const summary: WorkboardBoardSummary = {
|
||||
id: metadata.id,
|
||||
automationJobId: metadata.automationJobId,
|
||||
total: 0,
|
||||
active: 0,
|
||||
archived: 0,
|
||||
byStatus: {},
|
||||
};
|
||||
|
||||
expect(summary.automationJobId).toBe("job-categorize-planning");
|
||||
expectTypeOf(summary.automationJobId).toEqualTypeOf<string | undefined>();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||
import type { agentCommandFromIngress } from "./agent-runtime.js";
|
||||
import type { PluginRuntime } from "./core.js";
|
||||
|
||||
type PublicIngressOptions = Parameters<typeof agentCommandFromIngress>[0];
|
||||
type PublicExecutionStarted = NonNullable<PublicIngressOptions["onExecutionStarted"]>;
|
||||
type RuntimeIngressExecutionStarted = NonNullable<
|
||||
Parameters<PluginRuntime["agent"]["runCommandFromIngress"]>[0]["onExecutionStarted"]
|
||||
>;
|
||||
type RuntimeEmbeddedExecutionStarted = NonNullable<
|
||||
Parameters<PluginRuntime["agent"]["runEmbeddedAgent"]>[0]["onExecutionStarted"]
|
||||
>;
|
||||
const optionalRunIdCaller: PublicIngressOptions = {
|
||||
message: "hello",
|
||||
sessionKey: "agent:main:plugin-session",
|
||||
allowModelOverride: false,
|
||||
};
|
||||
const privateRecoveryCorrelationIsHidden: "executionIdentityAdmission" extends keyof PublicIngressOptions
|
||||
? false
|
||||
: true = true;
|
||||
|
||||
describe("public agent ingress correlation contract", () => {
|
||||
it("keeps runId optional and private execution recovery state unavailable", () => {
|
||||
expect(optionalRunIdCaller).not.toHaveProperty("runId");
|
||||
expect(privateRecoveryCorrelationIsHidden).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts ignored synchronous and asynchronous execution-start returns", () => {
|
||||
const observed: string[] = [];
|
||||
const synchronous = () => observed.push("started");
|
||||
const asynchronous = async () => {
|
||||
observed.push("started");
|
||||
};
|
||||
expectTypeOf(synchronous).toExtend<PublicExecutionStarted>();
|
||||
expectTypeOf(asynchronous).toExtend<PublicExecutionStarted>();
|
||||
expectTypeOf(synchronous).toExtend<RuntimeIngressExecutionStarted>();
|
||||
expectTypeOf(asynchronous).toExtend<RuntimeIngressExecutionStarted>();
|
||||
expectTypeOf(synchronous).toExtend<RuntimeEmbeddedExecutionStarted>();
|
||||
expectTypeOf(asynchronous).toExtend<RuntimeEmbeddedExecutionStarted>();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import type { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import { describe, expectTypeOf, it } from "vitest";
|
||||
|
||||
describe("agent sessions SDK", () => {
|
||||
it("keeps SessionManager.persist public", () => {
|
||||
expectTypeOf<SessionManager["persist"]>().toBeFunction();
|
||||
});
|
||||
|
||||
it("keeps tool-result preparation internal", () => {
|
||||
expectTypeOf<SessionManager>().not.toHaveProperty("prepareModelVisibleToolText");
|
||||
expectTypeOf<typeof import("openclaw/plugin-sdk/agent-sessions")>().not.toHaveProperty(
|
||||
"prepareSessionToolResult",
|
||||
);
|
||||
expectTypeOf<typeof import("openclaw/plugin-sdk/agent-sessions")>().not.toHaveProperty(
|
||||
"setSessionToolTextPreparer",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import { describe, expectTypeOf, it } from "vitest";
|
||||
import {
|
||||
resolveMemorySearchConfig,
|
||||
resolveMemorySearchSyncConfig,
|
||||
type ResolvedMemorySearchConfig,
|
||||
} from "./memory-core-host-engine-foundation.js";
|
||||
|
||||
type IsOptional<T, K extends keyof T> = Pick<T, K> extends Required<Pick<T, K>> ? false : true;
|
||||
|
||||
describe("memory core host engine foundation contracts", () => {
|
||||
it("preserves the released resolved-config authoring shapes", () => {
|
||||
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "inputType">>().toEqualTypeOf<true>();
|
||||
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "queryInputType">>().toEqualTypeOf<true>();
|
||||
expectTypeOf<
|
||||
IsOptional<ResolvedMemorySearchConfig, "documentInputType">
|
||||
>().toEqualTypeOf<true>();
|
||||
expectTypeOf<
|
||||
IsOptional<ResolvedMemorySearchConfig, "outputDimensionality">
|
||||
>().toEqualTypeOf<true>();
|
||||
expectTypeOf<
|
||||
IsOptional<ResolvedMemorySearchConfig["local"], "modelPath">
|
||||
>().toEqualTypeOf<true>();
|
||||
expectTypeOf<
|
||||
IsOptional<ResolvedMemorySearchConfig["store"]["vector"], "extensionPath">
|
||||
>().toEqualTypeOf<true>();
|
||||
expectTypeOf<
|
||||
ResolvedMemorySearchConfig["sync"]["embeddingBatchTimeoutSeconds"]
|
||||
>().toEqualTypeOf<number | undefined>();
|
||||
|
||||
const local: ResolvedMemorySearchConfig["local"] = {};
|
||||
const vector: ResolvedMemorySearchConfig["store"]["vector"] = { enabled: true };
|
||||
expectTypeOf(local).toMatchTypeOf<ResolvedMemorySearchConfig["local"]>();
|
||||
expectTypeOf(vector).toMatchTypeOf<ResolvedMemorySearchConfig["store"]["vector"]>();
|
||||
});
|
||||
|
||||
it("preserves the released resolver return contracts", () => {
|
||||
expectTypeOf<
|
||||
ReturnType<typeof resolveMemorySearchConfig>
|
||||
>().toEqualTypeOf<ResolvedMemorySearchConfig | null>();
|
||||
expectTypeOf<ReturnType<typeof resolveMemorySearchSyncConfig>>().toEqualTypeOf<
|
||||
ResolvedMemorySearchConfig["sync"] | null
|
||||
>();
|
||||
|
||||
expectTypeOf<
|
||||
NonNullable<ReturnType<typeof resolveMemorySearchConfig>>["local"]["contextSize"]
|
||||
>().toEqualTypeOf<number | "auto" | undefined>();
|
||||
expectTypeOf<
|
||||
NonNullable<ReturnType<typeof resolveMemorySearchSyncConfig>>["embeddingBatchTimeoutSeconds"]
|
||||
>().toEqualTypeOf<number | undefined>();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { describe, expectTypeOf, it } from "vitest";
|
||||
import type { PluginStateKeyedStore, PluginStateSyncKeyedStore } from "./plugin-state-runtime.js";
|
||||
import type {
|
||||
createPluginStateKeyedStore,
|
||||
createPluginStateSyncKeyedStore,
|
||||
} from "./plugin-state-store-runtime.js";
|
||||
|
||||
describe("plugin state store type contracts", () => {
|
||||
it("guarantees atomic capabilities from the concrete SDK factory", () => {
|
||||
expectTypeOf<ReturnType<typeof createPluginStateKeyedStore<{ count: number }>>>().toEqualTypeOf<
|
||||
Required<PluginStateKeyedStore<{ count: number }>>
|
||||
>();
|
||||
expectTypeOf<
|
||||
ReturnType<typeof createPluginStateSyncKeyedStore<{ count: number }>>
|
||||
>().toEqualTypeOf<Required<PluginStateSyncKeyedStore<{ count: number }>>>();
|
||||
});
|
||||
|
||||
it("allows general stores without optional capabilities", () => {
|
||||
expectTypeOf<
|
||||
Omit<PluginStateKeyedStore<{ count: number }>, "update" | "deleteIf" | "lookupMany">
|
||||
>().toExtend<PluginStateKeyedStore<{ count: number }>>();
|
||||
expectTypeOf<
|
||||
Omit<PluginStateSyncKeyedStore<{ count: number }>, "update" | "deleteIf" | "lookupMany">
|
||||
>().toExtend<PluginStateSyncKeyedStore<{ count: number }>>();
|
||||
});
|
||||
});
|
||||
|
|
@ -121,7 +121,7 @@ export const PLUGIN_COMPAT_RECORDS = [
|
|||
"src/plugins/compat/registry.test.ts",
|
||||
"src/plugin-state/plugin-state-store.test.ts",
|
||||
"src/plugin-state/plugin-state-store.runtime.test.ts",
|
||||
"src/plugin-sdk/plugin-state-store-runtime.test.ts",
|
||||
"test/type-contracts/plugin-state-store-runtime.ts",
|
||||
"src/plugins/loader.runtime-registry.test.ts",
|
||||
],
|
||||
releaseNote:
|
||||
|
|
|
|||
|
|
@ -133,9 +133,9 @@ describe("type suppression inventory", () => {
|
|||
"src/infra/net/fetch-guard.socks.test.ts:@ts-expect-error Undici's Node TLS intersection rejects its runtime-valid null timeout.",
|
||||
"src/infra/net/fetch-guard.socks.test.ts:@ts-expect-error Undici's Node TLS intersection rejects its runtime-valid null timeout.",
|
||||
"src/infra/net/fetch-guard.socks.test.ts:@ts-expect-error Undici's Node TLS intersection rejects its runtime-valid null timeout.",
|
||||
"src/plugin-sdk/plugin-entry.reply-trigger.test.ts:@ts-expect-error Trigger eligibility is only supported for before_agent_reply.",
|
||||
"src/plugin-sdk/plugin-entry.reply-trigger.test.ts:@ts-expect-error An empty trigger list cannot prove that a hook is inactive.",
|
||||
"src/plugin-sdk/plugin-entry.reply-trigger.test.ts:@ts-expect-error Tool authority is only supported for before_prompt_build.",
|
||||
"test/type-contracts/plugin-entry-hook-options.ts:@ts-expect-error Trigger eligibility is only supported for before_agent_reply.",
|
||||
"test/type-contracts/plugin-entry-hook-options.ts:@ts-expect-error An empty trigger list cannot prove that a hook is inactive.",
|
||||
"test/type-contracts/plugin-entry-hook-options.ts:@ts-expect-error Tool authority is only supported for before_prompt_build.",
|
||||
"src/plugins/registry.diagnostics.test.ts:@ts-expect-error JavaScript plugins may omit the required supplement builder.",
|
||||
"src/plugins/registry.diagnostics.test.ts:@ts-expect-error JavaScript plugins may omit the required hosted-media resolver.",
|
||||
"src/plugins/registry.diagnostics.test.ts:@ts-expect-error Unknown JavaScript hook names must produce a diagnostic.",
|
||||
|
|
|
|||
38
test/type-contracts/agent-runtime-ingress.ts
Normal file
38
test/type-contracts/agent-runtime-ingress.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { expectTypeOf } from "vitest";
|
||||
import type { agentCommandFromIngress } from "../../src/plugin-sdk/agent-runtime.js";
|
||||
import type { PluginRuntime } from "../../src/plugin-sdk/core.js";
|
||||
|
||||
type PublicIngressOptions = Parameters<typeof agentCommandFromIngress>[0];
|
||||
type PublicExecutionStarted = NonNullable<PublicIngressOptions["onExecutionStarted"]>;
|
||||
type RuntimeIngressExecutionStarted = NonNullable<
|
||||
Parameters<PluginRuntime["agent"]["runCommandFromIngress"]>[0]["onExecutionStarted"]
|
||||
>;
|
||||
type RuntimeEmbeddedExecutionStarted = NonNullable<
|
||||
Parameters<PluginRuntime["agent"]["runEmbeddedAgent"]>[0]["onExecutionStarted"]
|
||||
>;
|
||||
// public agent ingress correlation contract
|
||||
// keeps runId optional and private execution recovery state unavailable
|
||||
const optionalRunIdCaller: PublicIngressOptions = {
|
||||
message: "hello",
|
||||
sessionKey: "agent:main:plugin-session",
|
||||
allowModelOverride: false,
|
||||
};
|
||||
const privateRecoveryCorrelationIsHidden: "executionIdentityAdmission" extends keyof PublicIngressOptions
|
||||
? false
|
||||
: true = true;
|
||||
|
||||
void optionalRunIdCaller;
|
||||
void privateRecoveryCorrelationIsHidden;
|
||||
|
||||
// accepts ignored synchronous and asynchronous execution-start returns
|
||||
const observed: string[] = [];
|
||||
const synchronous = () => observed.push("started");
|
||||
const asynchronous = async () => {
|
||||
observed.push("started");
|
||||
};
|
||||
expectTypeOf(synchronous).toExtend<PublicExecutionStarted>();
|
||||
expectTypeOf(asynchronous).toExtend<PublicExecutionStarted>();
|
||||
expectTypeOf(synchronous).toExtend<RuntimeIngressExecutionStarted>();
|
||||
expectTypeOf(asynchronous).toExtend<RuntimeIngressExecutionStarted>();
|
||||
expectTypeOf(synchronous).toExtend<RuntimeEmbeddedExecutionStarted>();
|
||||
expectTypeOf(asynchronous).toExtend<RuntimeEmbeddedExecutionStarted>();
|
||||
15
test/type-contracts/agent-sessions.ts
Normal file
15
test/type-contracts/agent-sessions.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import type { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import { expectTypeOf } from "vitest";
|
||||
|
||||
// agent sessions SDK
|
||||
// keeps SessionManager.persist public
|
||||
expectTypeOf<SessionManager["persist"]>().toBeFunction();
|
||||
|
||||
// keeps tool-result preparation internal
|
||||
expectTypeOf<SessionManager>().not.toHaveProperty("prepareModelVisibleToolText");
|
||||
expectTypeOf<typeof import("openclaw/plugin-sdk/agent-sessions")>().not.toHaveProperty(
|
||||
"prepareSessionToolResult",
|
||||
);
|
||||
expectTypeOf<typeof import("openclaw/plugin-sdk/agent-sessions")>().not.toHaveProperty(
|
||||
"setSessionToolTextPreparer",
|
||||
);
|
||||
44
test/type-contracts/memory-core-host-engine-foundation.ts
Normal file
44
test/type-contracts/memory-core-host-engine-foundation.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { expectTypeOf } from "vitest";
|
||||
import {
|
||||
resolveMemorySearchConfig,
|
||||
resolveMemorySearchSyncConfig,
|
||||
type ResolvedMemorySearchConfig,
|
||||
} from "../../src/plugin-sdk/memory-core-host-engine-foundation.js";
|
||||
|
||||
type IsOptional<T, K extends keyof T> = Pick<T, K> extends Required<Pick<T, K>> ? false : true;
|
||||
|
||||
// memory core host engine foundation contracts
|
||||
// preserves the released resolved-config authoring shapes
|
||||
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "inputType">>().toEqualTypeOf<true>();
|
||||
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "queryInputType">>().toEqualTypeOf<true>();
|
||||
expectTypeOf<IsOptional<ResolvedMemorySearchConfig, "documentInputType">>().toEqualTypeOf<true>();
|
||||
expectTypeOf<
|
||||
IsOptional<ResolvedMemorySearchConfig, "outputDimensionality">
|
||||
>().toEqualTypeOf<true>();
|
||||
expectTypeOf<IsOptional<ResolvedMemorySearchConfig["local"], "modelPath">>().toEqualTypeOf<true>();
|
||||
expectTypeOf<
|
||||
IsOptional<ResolvedMemorySearchConfig["store"]["vector"], "extensionPath">
|
||||
>().toEqualTypeOf<true>();
|
||||
expectTypeOf<ResolvedMemorySearchConfig["sync"]["embeddingBatchTimeoutSeconds"]>().toEqualTypeOf<
|
||||
number | undefined
|
||||
>();
|
||||
|
||||
const local: ResolvedMemorySearchConfig["local"] = {};
|
||||
const vector: ResolvedMemorySearchConfig["store"]["vector"] = { enabled: true };
|
||||
expectTypeOf(local).toMatchTypeOf<ResolvedMemorySearchConfig["local"]>();
|
||||
expectTypeOf(vector).toMatchTypeOf<ResolvedMemorySearchConfig["store"]["vector"]>();
|
||||
|
||||
// preserves the released resolver return contracts
|
||||
expectTypeOf<
|
||||
ReturnType<typeof resolveMemorySearchConfig>
|
||||
>().toEqualTypeOf<ResolvedMemorySearchConfig | null>();
|
||||
expectTypeOf<ReturnType<typeof resolveMemorySearchSyncConfig>>().toEqualTypeOf<
|
||||
ResolvedMemorySearchConfig["sync"] | null
|
||||
>();
|
||||
|
||||
expectTypeOf<
|
||||
NonNullable<ReturnType<typeof resolveMemorySearchConfig>>["local"]["contextSize"]
|
||||
>().toEqualTypeOf<number | "auto" | undefined>();
|
||||
expectTypeOf<
|
||||
NonNullable<ReturnType<typeof resolveMemorySearchSyncConfig>>["embeddingBatchTimeoutSeconds"]
|
||||
>().toEqualTypeOf<number | undefined>();
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
import { describe, expectTypeOf, it } from "vitest";
|
||||
import type { OpenClawPluginApi, WorkerMachineOption, WorkerProvider } from "./plugin-entry.js";
|
||||
import type { PluginHookAgentTrigger } from "./types.js";
|
||||
import { expectTypeOf } from "vitest";
|
||||
import type {
|
||||
OpenClawPluginApi,
|
||||
WorkerMachineOption,
|
||||
WorkerProvider,
|
||||
} from "../../src/plugin-sdk/plugin-entry.js";
|
||||
import type { PluginHookAgentTrigger } from "../../src/plugin-sdk/types.js";
|
||||
|
||||
function registerScopedReplyHook(api: OpenClawPluginApi): void {
|
||||
api.on("before_agent_reply", async () => undefined, { eligibleTriggers: ["heartbeat", "cron"] });
|
||||
|
|
@ -32,25 +36,23 @@ function registerAuthorizedPromptHook(api: OpenClawPluginApi): void {
|
|||
void registerScopedReplyHook;
|
||||
void registerAuthorizedPromptHook;
|
||||
|
||||
describe("plugin entry hook option contracts", () => {
|
||||
it("exposes scoped reply and prompt authority options through the public plugin API", () => {
|
||||
expectTypeOf<OpenClawPluginApi["on"]>().toBeFunction();
|
||||
expectTypeOf<PluginHookAgentTrigger>().toEqualTypeOf<"cron" | "heartbeat" | "user">();
|
||||
expectTypeOf<WorkerMachineOption>().toEqualTypeOf<{
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly cpu?: number;
|
||||
readonly memoryGb?: number;
|
||||
readonly default?: boolean;
|
||||
readonly os?: string;
|
||||
}>();
|
||||
expectTypeOf<
|
||||
Awaited<ReturnType<NonNullable<WorkerProvider["listOperatingSystems"]>>>[number]
|
||||
>().toEqualTypeOf<{
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly default?: boolean;
|
||||
readonly disabledReason?: string;
|
||||
}>();
|
||||
});
|
||||
});
|
||||
// plugin entry hook option contracts
|
||||
// exposes scoped reply and prompt authority options through the public plugin API
|
||||
expectTypeOf<OpenClawPluginApi["on"]>().toBeFunction();
|
||||
expectTypeOf<PluginHookAgentTrigger>().toEqualTypeOf<"cron" | "heartbeat" | "user">();
|
||||
expectTypeOf<WorkerMachineOption>().toEqualTypeOf<{
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly cpu?: number;
|
||||
readonly memoryGb?: number;
|
||||
readonly default?: boolean;
|
||||
readonly os?: string;
|
||||
}>();
|
||||
expectTypeOf<
|
||||
Awaited<ReturnType<NonNullable<WorkerProvider["listOperatingSystems"]>>>[number]
|
||||
>().toEqualTypeOf<{
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly default?: boolean;
|
||||
readonly disabledReason?: string;
|
||||
}>();
|
||||
26
test/type-contracts/plugin-state-store-runtime.ts
Normal file
26
test/type-contracts/plugin-state-store-runtime.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { expectTypeOf } from "vitest";
|
||||
import type {
|
||||
PluginStateKeyedStore,
|
||||
PluginStateSyncKeyedStore,
|
||||
} from "../../src/plugin-sdk/plugin-state-runtime.js";
|
||||
import type {
|
||||
createPluginStateKeyedStore,
|
||||
createPluginStateSyncKeyedStore,
|
||||
} from "../../src/plugin-sdk/plugin-state-store-runtime.js";
|
||||
|
||||
// plugin state store type contracts
|
||||
// guarantees atomic capabilities from the concrete SDK factory
|
||||
expectTypeOf<ReturnType<typeof createPluginStateKeyedStore<{ count: number }>>>().toEqualTypeOf<
|
||||
Required<PluginStateKeyedStore<{ count: number }>>
|
||||
>();
|
||||
expectTypeOf<ReturnType<typeof createPluginStateSyncKeyedStore<{ count: number }>>>().toEqualTypeOf<
|
||||
Required<PluginStateSyncKeyedStore<{ count: number }>>
|
||||
>();
|
||||
|
||||
// allows general stores without optional capabilities
|
||||
expectTypeOf<
|
||||
Omit<PluginStateKeyedStore<{ count: number }>, "update" | "deleteIf" | "lookupMany">
|
||||
>().toExtend<PluginStateKeyedStore<{ count: number }>>();
|
||||
expectTypeOf<
|
||||
Omit<PluginStateSyncKeyedStore<{ count: number }>, "update" | "deleteIf" | "lookupMany">
|
||||
>().toExtend<PluginStateSyncKeyedStore<{ count: number }>>();
|
||||
24
test/type-contracts/workboard-board-automation.ts
Normal file
24
test/type-contracts/workboard-board-automation.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { expectTypeOf } from "vitest";
|
||||
import type {
|
||||
WorkboardBoardMetadata,
|
||||
WorkboardBoardSummary,
|
||||
} from "../../packages/workboard-contract/src/index.js";
|
||||
|
||||
// workboard board automation contract
|
||||
// carries the owning automation job reference in metadata and summaries
|
||||
const metadata: WorkboardBoardMetadata = {
|
||||
id: "planning",
|
||||
automationJobId: "job-categorize-planning",
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
};
|
||||
const summary: WorkboardBoardSummary = {
|
||||
id: metadata.id,
|
||||
automationJobId: metadata.automationJobId,
|
||||
total: 0,
|
||||
active: 0,
|
||||
archived: 0,
|
||||
byStatus: {},
|
||||
};
|
||||
|
||||
expectTypeOf(summary.automationJobId).toEqualTypeOf<string | undefined>();
|
||||
Loading…
Add table
Add a link
Reference in a new issue