@xtrape/capsule-contracts-node 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -2
- package/dist/index.cjs +177 -19
- package/dist/index.d.cts +1535 -67
- package/dist/index.d.ts +1535 -67
- package/dist/index.js +149 -19
- package/dist/src/index.d.ts +4245 -0
- package/dist/src/index.js +210 -0
- package/dist/tests/bus.spec.d.ts +1 -0
- package/dist/tests/bus.spec.js +52 -0
- package/dist/tests/schema-validation.spec.d.ts +1 -0
- package/dist/tests/schema-validation.spec.js +155 -0
- package/dist/tests/schema.spec.d.ts +1 -0
- package/dist/tests/schema.spec.js +223 -0
- package/package.json +10 -9
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export { z };
|
|
3
|
+
export const AgentStatus = ["PENDING", "ONLINE", "OFFLINE", "DISABLED", "REVOKED"];
|
|
4
|
+
export const CapsuleServiceStatus = ["UNKNOWN", "HEALTHY", "UNHEALTHY", "STALE", "OFFLINE"];
|
|
5
|
+
export const HealthStatus = ["UP", "DEGRADED", "DOWN", "UNKNOWN"];
|
|
6
|
+
export const CommandStatus = ["PENDING", "RUNNING", "SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED"];
|
|
7
|
+
export const DangerLevel = ["LOW", "MEDIUM", "HIGH"];
|
|
8
|
+
export const AuditActorType = ["USER", "AGENT", "SYSTEM"];
|
|
9
|
+
export const AuditResult = ["SUCCESS", "FAILURE"];
|
|
10
|
+
export const TokenStatus = ["ACTIVE", "REVOKED", "EXPIRED", "USED"];
|
|
11
|
+
export const AgentMode = ["embedded", "ophub"];
|
|
12
|
+
export const ServiceKind = ["business", "infrastructure", "system"];
|
|
13
|
+
export const ServiceKindSchema = z.enum(ServiceKind);
|
|
14
|
+
// `newId`, `idPrefixes`, and `IdPrefix` were removed in v0.2 — they were a
|
|
15
|
+
// runtime helper that did not belong in a contracts package, and they
|
|
16
|
+
// produced IDs in a different format than the server's actual `createId`
|
|
17
|
+
// (nanoid 21 here vs. randomBytes(12).base64url() on the backend). Consumers
|
|
18
|
+
// that need to mint IDs locally should wire their own factory; the schemas
|
|
19
|
+
// still accept any string whose prefix is one of `agt_` / `svc_` / `tok_` /
|
|
20
|
+
// etc. via `z.string().startsWith(...)`.
|
|
21
|
+
export const AnyJson = z.record(z.any());
|
|
22
|
+
export const AgentStatusSchema = z.enum(AgentStatus);
|
|
23
|
+
export const CapsuleServiceStatusSchema = z.enum(CapsuleServiceStatus);
|
|
24
|
+
export const HealthStatusSchema = z.enum(HealthStatus);
|
|
25
|
+
export const CommandStatusSchema = z.enum(CommandStatus);
|
|
26
|
+
export const DangerLevelSchema = z.enum(DangerLevel).default("LOW");
|
|
27
|
+
export const AgentModeSchema = z.enum(AgentMode);
|
|
28
|
+
export const UserSchema = z.object({ id: z.string().startsWith("usr_"), username: z.string(), displayName: z.string().nullable().optional(), createdAt: z.string(), updatedAt: z.string() });
|
|
29
|
+
export const UserRole = z.enum(["owner", "operator", "viewer"]);
|
|
30
|
+
export const createUserRequestSchema = z.object({ username: z.string().min(1), password: z.string().min(12), displayName: z.string().optional(), role: UserRole.default("viewer") });
|
|
31
|
+
export const updateUserRequestSchema = z.object({ displayName: z.string().optional(), role: UserRole.optional(), status: z.enum(["ACTIVE", "DISABLED"]).optional() });
|
|
32
|
+
export const resetUserPasswordRequestSchema = z.object({ password: z.string().min(12) });
|
|
33
|
+
export const AdminLoginRequestSchema = z.object({ username: z.string().min(1), password: z.string().min(1) });
|
|
34
|
+
export const adminLoginRequestSchema = AdminLoginRequestSchema;
|
|
35
|
+
export const AdminSessionSchema = z.object({ user: UserSchema, csrfToken: z.string(), expiresAt: z.string() });
|
|
36
|
+
export const AgentSchema = z.object({ id: z.string().startsWith("agt_"), code: z.string(), name: z.string().nullable().optional(), mode: AgentModeSchema, runtime: z.string().nullable().optional(), status: AgentStatusSchema, lastHeartbeatAt: z.string().nullable().optional(), createdAt: z.string(), updatedAt: z.string() });
|
|
37
|
+
export const RegistrationTokenSchema = z.object({ id: z.string().startsWith("tok_"), name: z.string(), status: z.enum(TokenStatus), expiresAt: z.string().nullable().optional(), usedAt: z.string().nullable().optional(), createdAt: z.string() });
|
|
38
|
+
export const CreateRegistrationTokenRequestSchema = z.object({ name: z.string().default("Default registration token").optional(), expiresInSeconds: z.number().int().min(60).optional() });
|
|
39
|
+
export const createRegistrationTokenRequestSchema = CreateRegistrationTokenRequestSchema;
|
|
40
|
+
export const CreateRegistrationTokenResponseSchema = RegistrationTokenSchema.extend({ rawToken: z.string().startsWith("opstage_reg_") });
|
|
41
|
+
export const ServiceCapabilitySchema = z.object({ name: z.string().min(1).max(120), label: z.string().max(200).optional(), description: z.string().max(2000).optional(), metadata: AnyJson.optional() });
|
|
42
|
+
export const serviceCapabilitySchema = ServiceCapabilitySchema;
|
|
43
|
+
export const EventMetadataSchema = z.object({ name: z.string().min(1).max(160), direction: z.enum(["publish", "subscribe"]).optional(), schema: AnyJson.optional(), designOnly: z.boolean().default(true), metadata: AnyJson.optional() });
|
|
44
|
+
export const eventMetadataSchema = EventMetadataSchema;
|
|
45
|
+
export const CapsuleManifestSchema = z.object({ kind: z.literal("CapsuleService"), schemaVersion: z.string().default("1.0").optional(), code: z.string().min(1), name: z.string().min(1), description: z.string().optional(), version: z.string().min(1), runtime: z.enum(["nodejs", "java", "python", "go", "other"]), agentMode: AgentModeSchema, capabilities: z.array(z.union([z.string(), ServiceCapabilitySchema])).optional(), events: z.array(EventMetadataSchema).optional(), labels: z.record(z.string()).optional() }).passthrough();
|
|
46
|
+
export const HealthReportInputSchema = z.object({ status: HealthStatusSchema, message: z.string().optional(), details: AnyJson.optional() });
|
|
47
|
+
export const healthReportInputSchema = HealthReportInputSchema;
|
|
48
|
+
export const ConfigItemInputSchema = z.object({ key: z.string(), label: z.string().optional(), type: z.string(), source: z.string().optional(), editable: z.boolean().optional(), sensitive: z.boolean().optional(), valuePreview: z.string().optional(), defaultValue: z.string().optional(), secretRef: z.string().optional() });
|
|
49
|
+
export const configItemInputSchema = ConfigItemInputSchema;
|
|
50
|
+
export const ActionDefinitionInputSchema = z.object({ name: z.string(), label: z.string(), description: z.string().optional(), dangerLevel: z.enum(DangerLevel).default("LOW").optional(), requiresConfirmation: z.boolean().optional(), category: z.string().optional(), order: z.number().int().optional(), inputSchema: AnyJson.optional(), outputSchema: AnyJson.optional(), timeoutSeconds: z.number().int().positive().optional() });
|
|
51
|
+
export const actionDefinitionInputSchema = ActionDefinitionInputSchema;
|
|
52
|
+
export const ReportedServiceSchema = z.object({
|
|
53
|
+
code: z.string().min(1).max(80),
|
|
54
|
+
name: z.string().min(1).max(200),
|
|
55
|
+
description: z.string().max(2000).optional(),
|
|
56
|
+
version: z.string().max(80).optional(),
|
|
57
|
+
runtime: z.string().optional(),
|
|
58
|
+
manifest: AnyJson,
|
|
59
|
+
capabilities: z.array(ServiceCapabilitySchema).optional(),
|
|
60
|
+
events: z.array(EventMetadataSchema).optional(),
|
|
61
|
+
health: HealthReportInputSchema.optional(),
|
|
62
|
+
configs: z.array(ConfigItemInputSchema).optional(),
|
|
63
|
+
actions: z.array(ActionDefinitionInputSchema).optional(),
|
|
64
|
+
serviceKind: ServiceKindSchema.optional(),
|
|
65
|
+
});
|
|
66
|
+
export const reportedServiceSchema = ReportedServiceSchema;
|
|
67
|
+
export const ServiceReportRequestSchema = z.object({ services: z.array(ReportedServiceSchema).min(1) });
|
|
68
|
+
export const serviceReportRequestSchema = ServiceReportRequestSchema;
|
|
69
|
+
export const RuntimeKindSchema = z.enum(["nodejs", "java", "python", "go", "other"]);
|
|
70
|
+
export const RegisterAgentRequestSchema = z.object({ registrationToken: z.string().startsWith("opstage_reg_"), agent: z.object({ code: z.string(), name: z.string().optional(), mode: AgentModeSchema.default("embedded"), runtime: RuntimeKindSchema.or(z.string()).optional() }), service: ReportedServiceSchema.optional() });
|
|
71
|
+
export const registerAgentRequestSchema = RegisterAgentRequestSchema;
|
|
72
|
+
export const RegisterAgentResponseSchema = z.object({ agentId: z.string().startsWith("agt_"), agentToken: z.string().startsWith("opstage_agent_"), heartbeatIntervalSeconds: z.number().int(), commandPollIntervalSeconds: z.number().int() });
|
|
73
|
+
export const AgentHeartbeatResponseSchema = z.object({ heartbeatIntervalSeconds: z.number().int(), commandPollIntervalSeconds: z.number().int() });
|
|
74
|
+
export const AgentHeartbeatRequestSchema = z.object({ serviceId: z.string().startsWith("svc_").optional(), health: HealthReportInputSchema.optional() });
|
|
75
|
+
export const agentHeartbeatRequestSchema = AgentHeartbeatRequestSchema;
|
|
76
|
+
export const CapsuleServiceSchema = z.object({ id: z.string().startsWith("svc_"), agentId: z.string().startsWith("agt_").optional(), code: z.string(), name: z.string(), description: z.string().nullable().optional(), version: z.string().nullable().optional(), runtime: z.string().nullable().optional(), status: CapsuleServiceStatusSchema, healthStatus: HealthStatusSchema.optional(), lastReportedAt: z.string().nullable().optional(), lastHealthAt: z.string().nullable().optional(), createdAt: z.string(), updatedAt: z.string() });
|
|
77
|
+
export const HealthReportSchema = z.object({ id: z.string().startsWith("hlr_"), serviceId: z.string().startsWith("svc_"), agentId: z.string().startsWith("agt_").optional(), status: HealthStatusSchema, message: z.string().nullable().optional(), details: AnyJson.optional(), reportedAt: z.string() });
|
|
78
|
+
export const ConfigItemSchema = z.object({ id: z.string().startsWith("cfg_"), serviceId: z.string().startsWith("svc_"), key: z.string(), label: z.string().nullable().optional(), type: z.string(), source: z.string().nullable().optional(), editable: z.boolean().default(false), sensitive: z.boolean(), valuePreview: z.string().nullable().optional(), defaultValue: z.string().nullable().optional(), secretRef: z.string().nullable().optional() });
|
|
79
|
+
export const ActionDefinitionSchema = z.object({ id: z.string().startsWith("act_"), serviceId: z.string().startsWith("svc_"), name: z.string(), label: z.string(), description: z.string().nullable().optional(), dangerLevel: z.enum(DangerLevel), requiresConfirmation: z.boolean(), category: z.string().optional(), order: z.number().int().optional(), inputSchema: AnyJson.optional(), outputSchema: AnyJson.optional(), timeoutSeconds: z.number().int().optional() });
|
|
80
|
+
export const CapsuleServiceDetailSchema = CapsuleServiceSchema.extend({ manifest: AnyJson.optional(), health: HealthReportSchema.nullable().optional(), configs: z.array(ConfigItemSchema).optional(), actions: z.array(ActionDefinitionSchema).optional() });
|
|
81
|
+
/**
|
|
82
|
+
* Shape returned by an Agent's action `prepare` handler when responding to an
|
|
83
|
+
* `ACTION_PREPARE` command. Used by the SDK as the contract between
|
|
84
|
+
* `prepare()` callbacks and the backend; the backend forwards it to the UI as
|
|
85
|
+
* the `data` field of the prepare command result.
|
|
86
|
+
*/
|
|
87
|
+
export const ActionPrepareResultSchema = z.object({
|
|
88
|
+
action: AnyJson.optional(),
|
|
89
|
+
inputSchema: AnyJson.optional(),
|
|
90
|
+
initialPayload: AnyJson.optional(),
|
|
91
|
+
currentState: AnyJson.optional(),
|
|
92
|
+
});
|
|
93
|
+
export const actionPrepareResultSchema = ActionPrepareResultSchema;
|
|
94
|
+
// v0.4 Capsule Bus experimental contracts. These schemas intentionally model
|
|
95
|
+
// a small Opstage-governed event-to-command surface, not a general message
|
|
96
|
+
// broker or workflow DSL. Names and compatibility may change before v1.0.
|
|
97
|
+
export const CapsuleBusExperimentalSchema = z.literal("v0.4-experimental");
|
|
98
|
+
export const BusRouteStatus = ["ENABLED", "DISABLED", "DRY_RUN"];
|
|
99
|
+
export const BusRouteStatusSchema = z.enum(BusRouteStatus);
|
|
100
|
+
export const BusEventEnvelopeSchema = z.object({
|
|
101
|
+
id: z.string().startsWith("bev_").optional(),
|
|
102
|
+
experimental: CapsuleBusExperimentalSchema.default("v0.4-experimental"),
|
|
103
|
+
eventType: z.string().min(1).max(160),
|
|
104
|
+
sourceServiceCode: z.string().min(1).max(80),
|
|
105
|
+
sourceServiceId: z.string().startsWith("svc_").optional(),
|
|
106
|
+
occurredAt: z.string().datetime().optional(),
|
|
107
|
+
correlationId: z.string().min(1).max(160).optional(),
|
|
108
|
+
causationId: z.string().min(1).max(160).optional(),
|
|
109
|
+
payload: AnyJson.default({}).optional(),
|
|
110
|
+
metadata: AnyJson.default({}).optional(),
|
|
111
|
+
});
|
|
112
|
+
export const PublishBusEventRequestSchema = BusEventEnvelopeSchema.omit({ id: true }).extend({
|
|
113
|
+
routePolicy: z.object({ dryRun: z.boolean().optional() }).optional(),
|
|
114
|
+
});
|
|
115
|
+
export const BusRouteRuleSchema = z.object({
|
|
116
|
+
id: z.string().startsWith("brt_").optional(),
|
|
117
|
+
name: z.string().min(1).max(160),
|
|
118
|
+
description: z.string().max(1000).optional(),
|
|
119
|
+
status: BusRouteStatusSchema.optional().default("DISABLED"),
|
|
120
|
+
match: z.object({
|
|
121
|
+
eventType: z.string().min(1).max(160),
|
|
122
|
+
sourceServiceCode: z.string().min(1).max(80).optional(),
|
|
123
|
+
}),
|
|
124
|
+
target: z.object({
|
|
125
|
+
serviceCode: z.string().min(1).max(80),
|
|
126
|
+
actionName: z.string().min(1).max(128),
|
|
127
|
+
}),
|
|
128
|
+
inputMapping: AnyJson.default({}).optional(),
|
|
129
|
+
metadata: AnyJson.default({}).optional(),
|
|
130
|
+
});
|
|
131
|
+
export const CreateBusRouteRuleRequestSchema = BusRouteRuleSchema.omit({ id: true });
|
|
132
|
+
export const BusCommandRequestSchema = z.object({
|
|
133
|
+
commandId: z.string().startsWith("cmd_").optional(),
|
|
134
|
+
commandType: z.literal("ACTION_EXECUTE").default("ACTION_EXECUTE"),
|
|
135
|
+
targetService: z.string().min(1).max(80),
|
|
136
|
+
actionName: z.string().min(1).max(128),
|
|
137
|
+
sourceEventId: z.string().startsWith("bev_").optional(),
|
|
138
|
+
payload: AnyJson.default({}).optional(),
|
|
139
|
+
metadata: z.object({
|
|
140
|
+
routingRuleId: z.string().startsWith("brt_").optional(),
|
|
141
|
+
correlationId: z.string().min(1).max(160).optional(),
|
|
142
|
+
}).catchall(z.any()).optional(),
|
|
143
|
+
});
|
|
144
|
+
export const busCommandRequestSchema = BusCommandRequestSchema;
|
|
145
|
+
export const BusRoutingRuleSchema = BusRouteRuleSchema;
|
|
146
|
+
export const busRoutingRuleSchema = BusRoutingRuleSchema;
|
|
147
|
+
export const BusRoutedCommandSchema = z.object({
|
|
148
|
+
routeId: z.string().startsWith("brt_"),
|
|
149
|
+
commandId: z.string().startsWith("cmd_").optional(),
|
|
150
|
+
dryRun: z.boolean(),
|
|
151
|
+
targetServiceCode: z.string(),
|
|
152
|
+
actionName: z.string(),
|
|
153
|
+
status: z.enum(["CREATED", "DRY_RUN", "SKIPPED", "FAILED"]),
|
|
154
|
+
errorCode: z.string().optional(),
|
|
155
|
+
errorMessage: z.string().optional(),
|
|
156
|
+
});
|
|
157
|
+
export const PublishBusEventResponseSchema = z.object({
|
|
158
|
+
eventId: z.string().startsWith("bev_"),
|
|
159
|
+
experimental: CapsuleBusExperimentalSchema,
|
|
160
|
+
routedCommands: z.array(BusRoutedCommandSchema),
|
|
161
|
+
});
|
|
162
|
+
export const BusEventRecordSchema = BusEventEnvelopeSchema.extend({
|
|
163
|
+
id: z.string().startsWith("bev_"),
|
|
164
|
+
agentId: z.string().startsWith("agt_"),
|
|
165
|
+
acceptedAt: z.string(),
|
|
166
|
+
routeCount: z.number().int(),
|
|
167
|
+
});
|
|
168
|
+
export const BusErrorCode = {
|
|
169
|
+
CAPSULE_BUS_DISABLED: "CAPSULE_BUS_DISABLED",
|
|
170
|
+
BUS_RATE_LIMITED: "BUS_RATE_LIMITED",
|
|
171
|
+
BUS_DEPTH_EXCEEDED: "BUS_DEPTH_EXCEEDED",
|
|
172
|
+
};
|
|
173
|
+
export const publishBusEventRequestSchema = PublishBusEventRequestSchema;
|
|
174
|
+
export const publishBusEventResponseSchema = PublishBusEventResponseSchema;
|
|
175
|
+
export const busEventEnvelopeSchema = BusEventEnvelopeSchema;
|
|
176
|
+
export const busRouteRuleSchema = BusRouteRuleSchema;
|
|
177
|
+
export const createBusRouteRuleRequestSchema = CreateBusRouteRuleRequestSchema;
|
|
178
|
+
export const CreateActionCommandRequestSchema = z.object({ payload: AnyJson.optional(), confirmation: z.boolean().optional() });
|
|
179
|
+
export const createActionCommandRequestSchema = CreateActionCommandRequestSchema;
|
|
180
|
+
export const CommandTypeSchema = z.enum(["ACTION_PREPARE", "ACTION_EXECUTE"]);
|
|
181
|
+
export const CommandSchema = z.object({ id: z.string().startsWith("cmd_"), agentId: z.string().startsWith("agt_"), serviceId: z.string().startsWith("svc_"), type: CommandTypeSchema, actionName: z.string(), status: CommandStatusSchema, payload: AnyJson.optional(), createdByUserId: z.string().startsWith("usr_").nullable().optional(), createdAt: z.string(), startedAt: z.string().nullable().optional(), completedAt: z.string().nullable().optional(), expiresAt: z.string().nullable().optional() });
|
|
182
|
+
export const ReportCommandResultRequestSchema = z.object({ success: z.boolean(), message: z.string().optional(), data: AnyJson.optional(), error: AnyJson.optional(), startedAt: z.string().optional(), finishedAt: z.string().optional() });
|
|
183
|
+
export const reportCommandResultRequestSchema = ReportCommandResultRequestSchema;
|
|
184
|
+
export const CommandResultSchema = z.object({ id: z.string().startsWith("crs_"), commandId: z.string().startsWith("cmd_"), success: z.boolean(), message: z.string().nullable().optional(), data: AnyJson.optional(), error: AnyJson.optional(), reportedAt: z.string() });
|
|
185
|
+
export const CommandDetailSchema = CommandSchema.extend({ result: CommandResultSchema.nullable().optional() });
|
|
186
|
+
export const AuditEventSchema = z.object({ id: z.string().startsWith("aud_"), actorType: z.enum(AuditActorType), actorId: z.string().nullable().optional(), action: z.string(), targetType: z.string().nullable().optional(), targetId: z.string().nullable().optional(), result: z.enum(AuditResult), message: z.string().nullable().optional(), metadata: AnyJson.optional(), createdAt: z.string() });
|
|
187
|
+
export const DashboardSummarySchema = z.object({ agentCounts: z.record(z.number().int()), serviceCounts: z.record(z.number().int()), commandCounts: z.record(z.number().int()), recentCommands: z.array(CommandSchema).optional(), recentAuditEvents: z.array(AuditEventSchema).optional() });
|
|
188
|
+
export const SystemHealthSchema = z.object({ status: z.enum(["UP", "DEGRADED", "DOWN"]), timestamp: z.string(), version: z.string(), edition: z.literal("ce"), database: z.object({ status: z.enum(["UP", "DEGRADED", "DOWN"]), kind: z.enum(["sqlite", "postgres", "mysql"]).optional(), latencyMs: z.number().int().optional() }), uptimeSeconds: z.number().int().optional() });
|
|
189
|
+
export const SystemVersionSchema = z.object({ version: z.string(), edition: z.literal("ce"), commit: z.string().optional(), buildTime: z.string().optional() });
|
|
190
|
+
export const ListQueryBase = z.object({ page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(20), sort: z.string().optional() });
|
|
191
|
+
export class HttpError extends Error {
|
|
192
|
+
httpStatus;
|
|
193
|
+
code;
|
|
194
|
+
publicMessage;
|
|
195
|
+
details;
|
|
196
|
+
constructor(httpStatus, code, publicMessage, details) {
|
|
197
|
+
super(publicMessage);
|
|
198
|
+
this.httpStatus = httpStatus;
|
|
199
|
+
this.code = code;
|
|
200
|
+
this.publicMessage = publicMessage;
|
|
201
|
+
this.details = details;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export function parseSort(value, allowed) { if (!value)
|
|
205
|
+
return []; return value.split(",").filter(Boolean).map(raw => { const desc = raw.startsWith("-"); const field = desc ? raw.slice(1) : raw; if (!allowed.includes(field))
|
|
206
|
+
throw new HttpError(422, "VALIDATION_FAILED", `Unknown sort field: ${field}`); return { field, direction: desc ? "desc" : "asc" }; }); }
|
|
207
|
+
export function paginate(items, page, pageSize, total) { return { data: items, pagination: { page, pageSize, total } }; }
|
|
208
|
+
export const ErrorCode = {
|
|
209
|
+
INTERNAL_ERROR: "INTERNAL_ERROR", VALIDATION_FAILED: "VALIDATION_FAILED", UNAUTHORIZED: "UNAUTHORIZED", FORBIDDEN: "FORBIDDEN", NOT_FOUND: "NOT_FOUND", CONFLICT: "CONFLICT", CSRF_INVALID: "CSRF_INVALID", ACTION_REQUIRES_CONFIRMATION: "ACTION_REQUIRES_CONFIRMATION", COMMAND_EXPIRED: "COMMAND_EXPIRED", TOKEN_REVOKED: "TOKEN_REVOKED", TOKEN_EXPIRED: "TOKEN_EXPIRED", AGENT_REVOKED: "AGENT_REVOKED", AGENT_DISABLED: "AGENT_DISABLED"
|
|
210
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BusEventEnvelopeSchema, CreateBusRouteRuleRequestSchema, PublishBusEventResponseSchema, BusCommandRequestSchema, BusRoutingRuleSchema, } from "../src/index";
|
|
3
|
+
describe("v0.4 experimental Capsule Bus contracts", () => {
|
|
4
|
+
it("validates a minimal event envelope", () => {
|
|
5
|
+
const parsed = BusEventEnvelopeSchema.parse({
|
|
6
|
+
eventType: "demo.item.created",
|
|
7
|
+
sourceServiceCode: "demo-worker",
|
|
8
|
+
payload: { itemId: "item_1" },
|
|
9
|
+
});
|
|
10
|
+
expect(parsed.experimental).toBe("v0.4-experimental");
|
|
11
|
+
});
|
|
12
|
+
it("validates an event-to-command route rule", () => {
|
|
13
|
+
const parsed = CreateBusRouteRuleRequestSchema.parse({
|
|
14
|
+
name: "Notify on item creation",
|
|
15
|
+
status: "DRY_RUN",
|
|
16
|
+
match: { eventType: "demo.item.created", sourceServiceCode: "demo-worker" },
|
|
17
|
+
target: { serviceCode: "demo-notifier", actionName: "notify" },
|
|
18
|
+
});
|
|
19
|
+
expect(parsed.status).toBe("DRY_RUN");
|
|
20
|
+
});
|
|
21
|
+
it("rejects non-experimental event envelopes", () => {
|
|
22
|
+
expect(() => BusEventEnvelopeSchema.parse({
|
|
23
|
+
experimental: "stable",
|
|
24
|
+
eventType: "demo.item.created",
|
|
25
|
+
sourceServiceCode: "demo-worker",
|
|
26
|
+
})).toThrow();
|
|
27
|
+
});
|
|
28
|
+
it("validates command requests linked to source events", () => {
|
|
29
|
+
const parsed = BusCommandRequestSchema.parse({
|
|
30
|
+
targetService: "demo-notifier",
|
|
31
|
+
actionName: "notify",
|
|
32
|
+
sourceEventId: "bev_123",
|
|
33
|
+
payload: { itemId: "item-1" },
|
|
34
|
+
metadata: { routingRuleId: "brt_123", correlationId: "corr-1" },
|
|
35
|
+
});
|
|
36
|
+
expect(parsed.commandType).toBe("ACTION_EXECUTE");
|
|
37
|
+
});
|
|
38
|
+
it("keeps the implementation-plan BusRoutingRule alias", () => {
|
|
39
|
+
expect(BusRoutingRuleSchema.parse({
|
|
40
|
+
name: "Alias route",
|
|
41
|
+
match: { eventType: "demo.item.created" },
|
|
42
|
+
target: { serviceCode: "demo-worker", actionName: "notify" },
|
|
43
|
+
}).status).toBe("DISABLED");
|
|
44
|
+
});
|
|
45
|
+
it("validates routed command responses", () => {
|
|
46
|
+
expect(PublishBusEventResponseSchema.parse({
|
|
47
|
+
eventId: "bev_123",
|
|
48
|
+
experimental: "v0.4-experimental",
|
|
49
|
+
routedCommands: [{ routeId: "brt_123", commandId: "cmd_123", dryRun: false, targetServiceCode: "svc", actionName: "notify", status: "CREATED" }],
|
|
50
|
+
}).routedCommands).toHaveLength(1);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { ActionDefinitionInputSchema, AgentModeSchema, AgentHeartbeatRequestSchema, CapsuleManifestSchema, CommandResultSchema, ConfigItemInputSchema, ErrorCode, HealthReportInputSchema, RegisterAgentRequestSchema, RegisterAgentResponseSchema, ReportCommandResultRequestSchema, ServiceReportRequestSchema, eventMetadataSchema, paginate, parseSort, serviceCapabilitySchema, } from "../src/index";
|
|
3
|
+
describe("schema validation", () => {
|
|
4
|
+
it("accepts a valid ophub agent registration request", () => {
|
|
5
|
+
const parsed = RegisterAgentRequestSchema.parse({
|
|
6
|
+
registrationToken: "opstage_reg_public_review_ophub",
|
|
7
|
+
agent: {
|
|
8
|
+
code: "demo-agent",
|
|
9
|
+
name: "Demo Agent",
|
|
10
|
+
mode: "ophub",
|
|
11
|
+
runtime: "nodejs",
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
expect(parsed.agent.mode).toBe("ophub");
|
|
15
|
+
});
|
|
16
|
+
it("accepts a OpHub registration and multi-service report metadata", () => {
|
|
17
|
+
const parsed = RegisterAgentRequestSchema.parse({
|
|
18
|
+
registrationToken: "opstage_reg_public_review_ophub",
|
|
19
|
+
agent: {
|
|
20
|
+
code: "ophub-a",
|
|
21
|
+
name: "OpHub A",
|
|
22
|
+
mode: "ophub",
|
|
23
|
+
runtime: "go",
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
expect(AgentModeSchema.parse("ophub")).toBe("ophub");
|
|
27
|
+
expect(parsed.agent.mode).toBe("ophub");
|
|
28
|
+
expect(() => ServiceReportRequestSchema.parse({
|
|
29
|
+
services: [
|
|
30
|
+
{
|
|
31
|
+
code: "svc-one",
|
|
32
|
+
name: "Service One",
|
|
33
|
+
manifest: {
|
|
34
|
+
kind: "CapsuleService",
|
|
35
|
+
code: "svc-one",
|
|
36
|
+
name: "Service One",
|
|
37
|
+
version: "0.3.0",
|
|
38
|
+
runtime: "nodejs",
|
|
39
|
+
agentMode: "ophub",
|
|
40
|
+
capabilities: [{ name: "accounts.sync", label: "Account sync" }],
|
|
41
|
+
events: [{ name: "account.synced", direction: "publish", designOnly: true }],
|
|
42
|
+
},
|
|
43
|
+
capabilities: [{ name: "accounts.sync" }],
|
|
44
|
+
events: [{ name: "account.synced", direction: "publish" }],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
code: "svc-two",
|
|
48
|
+
name: "Service Two",
|
|
49
|
+
manifest: {
|
|
50
|
+
kind: "CapsuleService",
|
|
51
|
+
code: "svc-two",
|
|
52
|
+
name: "Service Two",
|
|
53
|
+
version: "0.3.0",
|
|
54
|
+
runtime: "nodejs",
|
|
55
|
+
agentMode: "ophub",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
})).not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
it("rejects unsupported health and effective service statuses", () => {
|
|
62
|
+
expect(() => HealthReportInputSchema.parse({ status: "HEALTHY" })).toThrow();
|
|
63
|
+
expect(() => HealthReportInputSchema.parse({ status: "OFFLINE" })).toThrow();
|
|
64
|
+
expect(() => HealthReportInputSchema.parse({ status: "UP" })).not.toThrow();
|
|
65
|
+
});
|
|
66
|
+
it("rejects invalid registration credentials", () => {
|
|
67
|
+
expect(() => RegisterAgentResponseSchema.parse({
|
|
68
|
+
agentId: "svc_wrong",
|
|
69
|
+
agentToken: "opstage_agent_token",
|
|
70
|
+
heartbeatIntervalSeconds: 30,
|
|
71
|
+
commandPollIntervalSeconds: 5,
|
|
72
|
+
})).toThrow();
|
|
73
|
+
expect(() => RegisterAgentResponseSchema.parse({
|
|
74
|
+
agentId: "agt_123",
|
|
75
|
+
agentToken: "plain-token",
|
|
76
|
+
heartbeatIntervalSeconds: 30,
|
|
77
|
+
commandPollIntervalSeconds: 5,
|
|
78
|
+
})).toThrow();
|
|
79
|
+
});
|
|
80
|
+
it("accepts optional manifest and action fields", () => {
|
|
81
|
+
const manifest = CapsuleManifestSchema.parse({
|
|
82
|
+
kind: "CapsuleService",
|
|
83
|
+
code: "demo-service",
|
|
84
|
+
name: "Demo Service",
|
|
85
|
+
version: "0.1.0",
|
|
86
|
+
runtime: "nodejs",
|
|
87
|
+
agentMode: "embedded",
|
|
88
|
+
});
|
|
89
|
+
const action = ActionDefinitionInputSchema.parse({
|
|
90
|
+
name: "sync",
|
|
91
|
+
label: "Sync",
|
|
92
|
+
});
|
|
93
|
+
expect(manifest.schemaVersion).toBeUndefined();
|
|
94
|
+
expect(action.dangerLevel).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
it("validates capability and event metadata schemas", () => {
|
|
97
|
+
expect(serviceCapabilitySchema.parse({ name: "backup.create", metadata: { target: "sqlite" } }).name).toBe("backup.create");
|
|
98
|
+
expect(eventMetadataSchema.parse({ name: "backup.created", direction: "publish" })).toMatchObject({
|
|
99
|
+
direction: "publish",
|
|
100
|
+
designOnly: true,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
it("validates service reports, heartbeats, and command results", () => {
|
|
104
|
+
expect(() => ServiceReportRequestSchema.parse({
|
|
105
|
+
services: [
|
|
106
|
+
{
|
|
107
|
+
code: "demo-service",
|
|
108
|
+
name: "Demo Service",
|
|
109
|
+
runtime: "nodejs",
|
|
110
|
+
manifest: {},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
})).not.toThrow();
|
|
114
|
+
expect(() => ServiceReportRequestSchema.parse({ services: [] })).toThrow();
|
|
115
|
+
expect(() => AgentHeartbeatRequestSchema.parse({ serviceId: "svc_123", health: { status: "DEGRADED" } })).not.toThrow();
|
|
116
|
+
expect(() => AgentHeartbeatRequestSchema.parse({ serviceId: "agt_wrong" })).toThrow();
|
|
117
|
+
expect(() => ReportCommandResultRequestSchema.parse({ success: true, data: { refreshed: true } })).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
it("validates config item inputs and persisted command results", () => {
|
|
120
|
+
expect(() => ConfigItemInputSchema.parse({
|
|
121
|
+
key: "apiKey",
|
|
122
|
+
label: "API key",
|
|
123
|
+
type: "secret",
|
|
124
|
+
sensitive: true,
|
|
125
|
+
secretRef: "secret://api-key",
|
|
126
|
+
})).not.toThrow();
|
|
127
|
+
expect(() => CommandResultSchema.parse({
|
|
128
|
+
id: "crs_123",
|
|
129
|
+
commandId: "cmd_123",
|
|
130
|
+
success: false,
|
|
131
|
+
message: "failed",
|
|
132
|
+
error: { code: "ACTION_FAILED" },
|
|
133
|
+
reportedAt: "2026-05-06T00:00:00.000Z",
|
|
134
|
+
})).not.toThrow();
|
|
135
|
+
expect(() => CommandResultSchema.parse({
|
|
136
|
+
id: "cmd_wrong",
|
|
137
|
+
commandId: "cmd_123",
|
|
138
|
+
success: true,
|
|
139
|
+
reportedAt: "2026-05-06T00:00:00.000Z",
|
|
140
|
+
})).toThrow();
|
|
141
|
+
});
|
|
142
|
+
it("exports public error codes and pagination helpers", () => {
|
|
143
|
+
expect(ErrorCode.VALIDATION_FAILED).toBe("VALIDATION_FAILED");
|
|
144
|
+
expect(ErrorCode.ACTION_REQUIRES_CONFIRMATION).toBe("ACTION_REQUIRES_CONFIRMATION");
|
|
145
|
+
expect(parseSort("-createdAt,name", ["createdAt", "name"])).toEqual([
|
|
146
|
+
{ field: "createdAt", direction: "desc" },
|
|
147
|
+
{ field: "name", direction: "asc" },
|
|
148
|
+
]);
|
|
149
|
+
expect(() => parseSort("unknown", ["createdAt"])).toThrow("Unknown sort field: unknown");
|
|
150
|
+
expect(paginate([{ id: "svc_1" }], 1, 20, 1)).toEqual({
|
|
151
|
+
data: [{ id: "svc_1" }],
|
|
152
|
+
pagination: { page: 1, pageSize: 20, total: 1 },
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { AgentStatus, CapsuleServiceStatus, ErrorCode, AgentStatusSchema, CapsuleServiceStatusSchema, HealthStatusSchema, CommandStatusSchema, DangerLevelSchema, CapsuleManifestSchema, healthReportInputSchema, configItemInputSchema, actionDefinitionInputSchema, reportedServiceSchema, serviceReportRequestSchema, registerAgentRequestSchema, RegisterAgentResponseSchema, AgentHeartbeatResponseSchema, agentHeartbeatRequestSchema, ConfigItemSchema, ActionDefinitionSchema, CommandSchema, reportCommandResultRequestSchema, CommandResultSchema, ListQueryBase, parseSort, paginate, HttpError } from "../src/index";
|
|
3
|
+
describe("Schema Validation", () => {
|
|
4
|
+
// Test enum schemas
|
|
5
|
+
it("validates AgentStatusSchema", () => {
|
|
6
|
+
expect(AgentStatusSchema.parse("ONLINE")).toBe("ONLINE");
|
|
7
|
+
expect(() => AgentStatusSchema.parse("INVALID")).toThrow();
|
|
8
|
+
});
|
|
9
|
+
it("validates CapsuleServiceStatusSchema", () => {
|
|
10
|
+
expect(CapsuleServiceStatusSchema.parse("HEALTHY")).toBe("HEALTHY");
|
|
11
|
+
expect(() => CapsuleServiceStatusSchema.parse("INVALID")).toThrow();
|
|
12
|
+
});
|
|
13
|
+
it("validates HealthStatusSchema", () => {
|
|
14
|
+
expect(HealthStatusSchema.parse("UP")).toBe("UP");
|
|
15
|
+
expect(() => HealthStatusSchema.parse("INVALID")).toThrow();
|
|
16
|
+
});
|
|
17
|
+
it("validates CommandStatusSchema", () => {
|
|
18
|
+
expect(CommandStatusSchema.parse("SUCCEEDED")).toBe("SUCCEEDED");
|
|
19
|
+
expect(() => CommandStatusSchema.parse("INVALID")).toThrow();
|
|
20
|
+
});
|
|
21
|
+
it("validates DangerLevelSchema", () => {
|
|
22
|
+
expect(DangerLevelSchema.parse("HIGH")).toBe("HIGH");
|
|
23
|
+
expect(DangerLevelSchema.parse(undefined)).toBe("LOW"); // default
|
|
24
|
+
});
|
|
25
|
+
// Test Agent registration schemas
|
|
26
|
+
it("validates RegisterAgentRequestSchema", () => {
|
|
27
|
+
const validRequest = {
|
|
28
|
+
registrationToken: "opstage_reg_abc123",
|
|
29
|
+
agent: {
|
|
30
|
+
code: "test-agent",
|
|
31
|
+
mode: "embedded"
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
expect(registerAgentRequestSchema.parse(validRequest)).toEqual(validRequest);
|
|
35
|
+
expect(() => registerAgentRequestSchema.parse({
|
|
36
|
+
registrationToken: "invalid_token",
|
|
37
|
+
agent: { code: "test", mode: "embedded" }
|
|
38
|
+
})).toThrow();
|
|
39
|
+
});
|
|
40
|
+
it("validates RegisterAgentResponseSchema", () => {
|
|
41
|
+
const validResponse = {
|
|
42
|
+
agentId: "agt_abc123def456",
|
|
43
|
+
agentToken: "opstage_agent_xyz789",
|
|
44
|
+
heartbeatIntervalSeconds: 30,
|
|
45
|
+
commandPollIntervalSeconds: 10
|
|
46
|
+
};
|
|
47
|
+
expect(RegisterAgentResponseSchema.parse(validResponse)).toEqual(validResponse);
|
|
48
|
+
});
|
|
49
|
+
// Test Service report schemas
|
|
50
|
+
it("validates CapsuleManifestSchema", () => {
|
|
51
|
+
const validManifest = {
|
|
52
|
+
kind: "CapsuleService",
|
|
53
|
+
code: "test-service",
|
|
54
|
+
name: "Test Service",
|
|
55
|
+
version: "1.0.0",
|
|
56
|
+
runtime: "nodejs",
|
|
57
|
+
agentMode: "embedded"
|
|
58
|
+
};
|
|
59
|
+
expect(CapsuleManifestSchema.parse(validManifest)).toEqual(validManifest);
|
|
60
|
+
});
|
|
61
|
+
it("validates ReportedServiceSchema", () => {
|
|
62
|
+
const validService = {
|
|
63
|
+
code: "test-service",
|
|
64
|
+
name: "Test Service",
|
|
65
|
+
manifest: {}
|
|
66
|
+
};
|
|
67
|
+
expect(reportedServiceSchema.parse(validService)).toEqual(validService);
|
|
68
|
+
});
|
|
69
|
+
it("validates ServiceReportRequestSchema", () => {
|
|
70
|
+
const validRequest = {
|
|
71
|
+
services: [{
|
|
72
|
+
code: "test-service",
|
|
73
|
+
name: "Test Service",
|
|
74
|
+
manifest: {}
|
|
75
|
+
}]
|
|
76
|
+
};
|
|
77
|
+
expect(serviceReportRequestSchema.parse(validRequest)).toEqual(validRequest);
|
|
78
|
+
});
|
|
79
|
+
// Test Health report schemas
|
|
80
|
+
it("validates HealthReportInputSchema", () => {
|
|
81
|
+
const validHealth = {
|
|
82
|
+
status: "UP",
|
|
83
|
+
message: "All good"
|
|
84
|
+
};
|
|
85
|
+
expect(healthReportInputSchema.parse(validHealth)).toEqual(validHealth);
|
|
86
|
+
});
|
|
87
|
+
it("validates AgentHeartbeatRequestSchema", () => {
|
|
88
|
+
const validHeartbeat = {
|
|
89
|
+
health: {
|
|
90
|
+
status: "UP"
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
expect(agentHeartbeatRequestSchema.parse(validHeartbeat)).toEqual(validHeartbeat);
|
|
94
|
+
});
|
|
95
|
+
it("validates AgentHeartbeatResponseSchema", () => {
|
|
96
|
+
const validResponse = {
|
|
97
|
+
heartbeatIntervalSeconds: 30,
|
|
98
|
+
commandPollIntervalSeconds: 10
|
|
99
|
+
};
|
|
100
|
+
expect(AgentHeartbeatResponseSchema.parse(validResponse)).toEqual(validResponse);
|
|
101
|
+
});
|
|
102
|
+
// Test Config item schemas
|
|
103
|
+
it("validates ConfigItemInputSchema", () => {
|
|
104
|
+
const validConfig = {
|
|
105
|
+
key: "database_url",
|
|
106
|
+
type: "string",
|
|
107
|
+
sensitive: true
|
|
108
|
+
};
|
|
109
|
+
expect(configItemInputSchema.parse(validConfig)).toEqual(validConfig);
|
|
110
|
+
});
|
|
111
|
+
it("validates ConfigItemSchema", () => {
|
|
112
|
+
const validConfig = {
|
|
113
|
+
id: "cfg_abc123def456",
|
|
114
|
+
serviceId: "svc_abc123def456",
|
|
115
|
+
key: "database_url",
|
|
116
|
+
type: "string",
|
|
117
|
+
sensitive: true,
|
|
118
|
+
editable: false // default value from schema
|
|
119
|
+
};
|
|
120
|
+
expect(ConfigItemSchema.parse(validConfig)).toEqual(validConfig);
|
|
121
|
+
});
|
|
122
|
+
// Test Action definition schemas
|
|
123
|
+
it("validates ActionDefinitionInputSchema", () => {
|
|
124
|
+
const validAction = {
|
|
125
|
+
name: "restart",
|
|
126
|
+
label: "Restart Service",
|
|
127
|
+
dangerLevel: "MEDIUM"
|
|
128
|
+
};
|
|
129
|
+
expect(actionDefinitionInputSchema.parse(validAction)).toEqual(validAction);
|
|
130
|
+
});
|
|
131
|
+
it("validates ActionDefinitionSchema", () => {
|
|
132
|
+
const validAction = {
|
|
133
|
+
id: "act_abc123def456",
|
|
134
|
+
serviceId: "svc_abc123def456",
|
|
135
|
+
name: "restart",
|
|
136
|
+
label: "Restart Service",
|
|
137
|
+
dangerLevel: "MEDIUM",
|
|
138
|
+
requiresConfirmation: false
|
|
139
|
+
};
|
|
140
|
+
expect(ActionDefinitionSchema.parse(validAction)).toEqual(validAction);
|
|
141
|
+
});
|
|
142
|
+
// Test Command schemas
|
|
143
|
+
it("validates CommandSchema", () => {
|
|
144
|
+
const validCommand = {
|
|
145
|
+
id: "cmd_abc123def456",
|
|
146
|
+
agentId: "agt_abc123def456",
|
|
147
|
+
serviceId: "svc_abc123def456",
|
|
148
|
+
type: "ACTION_EXECUTE",
|
|
149
|
+
actionName: "restart",
|
|
150
|
+
status: "PENDING",
|
|
151
|
+
createdAt: "2026-05-06T13:22:30Z"
|
|
152
|
+
};
|
|
153
|
+
expect(CommandSchema.parse(validCommand)).toEqual(validCommand);
|
|
154
|
+
});
|
|
155
|
+
it("validates ReportCommandResultRequestSchema", () => {
|
|
156
|
+
const validResult = {
|
|
157
|
+
success: true,
|
|
158
|
+
message: "Command completed successfully"
|
|
159
|
+
};
|
|
160
|
+
expect(reportCommandResultRequestSchema.parse(validResult)).toEqual(validResult);
|
|
161
|
+
});
|
|
162
|
+
it("validates CommandResultSchema", () => {
|
|
163
|
+
const validResult = {
|
|
164
|
+
id: "crs_abc123def456",
|
|
165
|
+
commandId: "cmd_abc123def456",
|
|
166
|
+
success: true,
|
|
167
|
+
reportedAt: "2026-05-06T13:22:30Z"
|
|
168
|
+
};
|
|
169
|
+
expect(CommandResultSchema.parse(validResult)).toEqual(validResult);
|
|
170
|
+
});
|
|
171
|
+
// Test Error codes
|
|
172
|
+
it("exports ErrorCode correctly", () => {
|
|
173
|
+
expect(ErrorCode.INTERNAL_ERROR).toBe("INTERNAL_ERROR");
|
|
174
|
+
expect(ErrorCode.UNAUTHORIZED).toBe("UNAUTHORIZED");
|
|
175
|
+
expect(Object.values(ErrorCode)).toContain("VALIDATION_FAILED");
|
|
176
|
+
});
|
|
177
|
+
// Test pagination helpers
|
|
178
|
+
it("ListQueryBase validates pagination", () => {
|
|
179
|
+
expect(ListQueryBase.parse({})).toEqual({ page: 1, pageSize: 20 });
|
|
180
|
+
expect(ListQueryBase.parse({ page: 2, pageSize: 50 })).toEqual({ page: 2, pageSize: 50 });
|
|
181
|
+
expect(() => ListQueryBase.parse({ pageSize: 200 })).toThrow(); // max 100
|
|
182
|
+
expect(() => ListQueryBase.parse({ page: 0 })).toThrow(); // min 1
|
|
183
|
+
});
|
|
184
|
+
it("parseSort handles sort fields", () => {
|
|
185
|
+
const allowedFields = ["name", "createdAt"];
|
|
186
|
+
expect(parseSort("name,-createdAt", allowedFields)).toEqual([
|
|
187
|
+
{ field: "name", direction: "asc" },
|
|
188
|
+
{ field: "createdAt", direction: "desc" }
|
|
189
|
+
]);
|
|
190
|
+
expect(() => parseSort("invalidField", allowedFields)).toThrow(HttpError);
|
|
191
|
+
});
|
|
192
|
+
it("paginate creates pagination object", () => {
|
|
193
|
+
const items = [1, 2, 3];
|
|
194
|
+
const result = paginate(items, 1, 10, 3);
|
|
195
|
+
expect(result).toEqual({
|
|
196
|
+
data: items,
|
|
197
|
+
pagination: { page: 1, pageSize: 10, total: 3 }
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
// Test that README examples match actual exports
|
|
201
|
+
it("README examples should match actual exports", () => {
|
|
202
|
+
// This ensures we don't have drift between documentation and implementation
|
|
203
|
+
expect(AgentStatus).toBeDefined();
|
|
204
|
+
expect(CapsuleServiceStatus).toBeDefined();
|
|
205
|
+
expect(ErrorCode).toBeDefined();
|
|
206
|
+
});
|
|
207
|
+
// Test that schemas don't over-constrain future draft fields
|
|
208
|
+
it("schemas allow additional fields via passthrough", () => {
|
|
209
|
+
const manifestWithExtra = {
|
|
210
|
+
kind: "CapsuleService",
|
|
211
|
+
code: "test",
|
|
212
|
+
name: "Test",
|
|
213
|
+
version: "1.0.0",
|
|
214
|
+
runtime: "nodejs",
|
|
215
|
+
agentMode: "embedded",
|
|
216
|
+
// Additional fields that might be added in future
|
|
217
|
+
experimentalFeature: true,
|
|
218
|
+
metadata: { custom: "value" }
|
|
219
|
+
};
|
|
220
|
+
// Should not throw - passthrough allows extra fields
|
|
221
|
+
expect(CapsuleManifestSchema.parse(manifestWithExtra)).toEqual(manifestWithExtra);
|
|
222
|
+
});
|
|
223
|
+
});
|