@adcp/client 4.14.0 → 4.16.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/bin/adcp.js +2 -0
- package/dist/lib/adapters/governance-adapter.d.ts +2 -2
- package/dist/lib/adapters/governance-adapter.d.ts.map +1 -1
- package/dist/lib/adapters/governance-adapter.js +1 -3
- package/dist/lib/adapters/governance-adapter.js.map +1 -1
- package/dist/lib/adapters/si-session-manager.d.ts.map +1 -1
- package/dist/lib/adapters/si-session-manager.js +8 -3
- package/dist/lib/adapters/si-session-manager.js.map +1 -1
- package/dist/lib/agents/index.generated.d.ts +9 -1
- package/dist/lib/agents/index.generated.d.ts.map +1 -1
- package/dist/lib/agents/index.generated.js +12 -0
- package/dist/lib/agents/index.generated.js.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.d.ts.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.js +10 -3
- package/dist/lib/core/ADCPMultiAgentClient.js.map +1 -1
- package/dist/lib/core/AgentClient.d.ts.map +1 -1
- package/dist/lib/core/AsyncHandler.d.ts.map +1 -1
- package/dist/lib/core/AsyncHandler.js +1 -1
- package/dist/lib/core/AsyncHandler.js.map +1 -1
- package/dist/lib/core/GovernanceMiddleware.d.ts +2 -10
- package/dist/lib/core/GovernanceMiddleware.d.ts.map +1 -1
- package/dist/lib/core/GovernanceMiddleware.js +8 -51
- package/dist/lib/core/GovernanceMiddleware.js.map +1 -1
- package/dist/lib/core/GovernanceTypes.d.ts +4 -4
- package/dist/lib/core/GovernanceTypes.d.ts.map +1 -1
- package/dist/lib/core/GovernanceTypes.js +1 -0
- package/dist/lib/core/GovernanceTypes.js.map +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.js +7 -4
- package/dist/lib/core/SingleAgentClient.js.map +1 -1
- package/dist/lib/core/TaskExecutor.d.ts +4 -0
- package/dist/lib/core/TaskExecutor.d.ts.map +1 -1
- package/dist/lib/core/TaskExecutor.js +61 -18
- package/dist/lib/core/TaskExecutor.js.map +1 -1
- package/dist/lib/index.d.ts +5 -3
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +12 -6
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/protocols/index.d.ts +1 -0
- package/dist/lib/protocols/index.d.ts.map +1 -1
- package/dist/lib/protocols/index.js +14 -4
- package/dist/lib/protocols/index.js.map +1 -1
- package/dist/lib/protocols/mcp-tasks.d.ts +55 -0
- package/dist/lib/protocols/mcp-tasks.d.ts.map +1 -0
- package/dist/lib/protocols/mcp-tasks.js +334 -0
- package/dist/lib/protocols/mcp-tasks.js.map +1 -0
- package/dist/lib/protocols/mcp.d.ts +9 -0
- package/dist/lib/protocols/mcp.d.ts.map +1 -1
- package/dist/lib/protocols/mcp.js +4 -0
- package/dist/lib/protocols/mcp.js.map +1 -1
- package/dist/lib/registry/types.generated.d.ts +9 -9
- package/dist/lib/registry/types.generated.d.ts.map +1 -1
- package/dist/lib/registry/types.generated.js +1 -1
- package/dist/lib/server/index.d.ts +2 -0
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +7 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/tasks.d.ts +86 -0
- package/dist/lib/server/tasks.d.ts.map +1 -0
- package/dist/lib/server/tasks.js +110 -0
- package/dist/lib/server/tasks.js.map +1 -0
- package/dist/lib/testing/agent-tester.d.ts +1 -1
- package/dist/lib/testing/agent-tester.d.ts.map +1 -1
- package/dist/lib/testing/agent-tester.js +52 -2
- package/dist/lib/testing/agent-tester.js.map +1 -1
- package/dist/lib/testing/client.d.ts +18 -0
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +39 -1
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts +4 -0
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +406 -173
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/types.d.ts +7 -1
- package/dist/lib/testing/compliance/types.d.ts.map +1 -1
- package/dist/lib/testing/index.d.ts +3 -1
- package/dist/lib/testing/index.d.ts.map +1 -1
- package/dist/lib/testing/index.js +13 -2
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/orchestrator.d.ts +4 -0
- package/dist/lib/testing/orchestrator.d.ts.map +1 -1
- package/dist/lib/testing/orchestrator.js +19 -2
- package/dist/lib/testing/orchestrator.js.map +1 -1
- package/dist/lib/testing/scenarios/capabilities.js +2 -2
- package/dist/lib/testing/scenarios/capabilities.js.map +1 -1
- package/dist/lib/testing/scenarios/creative.js +4 -4
- package/dist/lib/testing/scenarios/creative.js.map +1 -1
- package/dist/lib/testing/scenarios/deterministic.d.ts +37 -0
- package/dist/lib/testing/scenarios/deterministic.d.ts.map +1 -0
- package/dist/lib/testing/scenarios/deterministic.js +705 -0
- package/dist/lib/testing/scenarios/deterministic.js.map +1 -0
- package/dist/lib/testing/scenarios/discovery.js +2 -2
- package/dist/lib/testing/scenarios/discovery.js.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.js +18 -25
- package/dist/lib/testing/scenarios/edge-cases.js.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.js +9 -13
- package/dist/lib/testing/scenarios/error-compliance.js.map +1 -1
- package/dist/lib/testing/scenarios/governance.d.ts +15 -0
- package/dist/lib/testing/scenarios/governance.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/governance.js +386 -49
- package/dist/lib/testing/scenarios/governance.js.map +1 -1
- package/dist/lib/testing/scenarios/health.js +2 -2
- package/dist/lib/testing/scenarios/health.js.map +1 -1
- package/dist/lib/testing/scenarios/index.d.ts +2 -1
- package/dist/lib/testing/scenarios/index.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/index.js +12 -1
- package/dist/lib/testing/scenarios/index.js.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.js +258 -31
- package/dist/lib/testing/scenarios/media-buy.js.map +1 -1
- package/dist/lib/testing/scenarios/schema-compliance.js +2 -2
- package/dist/lib/testing/scenarios/schema-compliance.js.map +1 -1
- package/dist/lib/testing/scenarios/signals.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/signals.js +35 -2
- package/dist/lib/testing/scenarios/signals.js.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.js +8 -7
- package/dist/lib/testing/scenarios/sponsored-intelligence.js.map +1 -1
- package/dist/lib/testing/stubs/governance-agent-stub.d.ts +72 -0
- package/dist/lib/testing/stubs/governance-agent-stub.d.ts.map +1 -0
- package/dist/lib/testing/stubs/governance-agent-stub.js +295 -0
- package/dist/lib/testing/stubs/governance-agent-stub.js.map +1 -0
- package/dist/lib/testing/stubs/index.d.ts +3 -0
- package/dist/lib/testing/stubs/index.d.ts.map +1 -0
- package/dist/lib/testing/stubs/index.js +6 -0
- package/dist/lib/testing/stubs/index.js.map +1 -0
- package/dist/lib/testing/test-controller.d.ts +46 -0
- package/dist/lib/testing/test-controller.d.ts.map +1 -0
- package/dist/lib/testing/test-controller.js +143 -0
- package/dist/lib/testing/test-controller.js.map +1 -0
- package/dist/lib/testing/types.d.ts +8 -1
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/types/core.generated.d.ts +562 -97
- package/dist/lib/types/core.generated.d.ts.map +1 -1
- package/dist/lib/types/core.generated.js +1 -1
- package/dist/lib/types/error-codes.d.ts +4 -4
- package/dist/lib/types/error-codes.d.ts.map +1 -1
- package/dist/lib/types/error-codes.js +26 -2
- package/dist/lib/types/error-codes.js.map +1 -1
- package/dist/lib/types/schemas.generated.d.ts +4625 -8682
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +711 -403
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/types/tools.generated.d.ts +1188 -405
- package/dist/lib/types/tools.generated.d.ts.map +1 -1
- package/dist/lib/utils/response-schemas.d.ts.map +1 -1
- package/dist/lib/utils/response-schemas.js +2 -0
- package/dist/lib/utils/response-schemas.js.map +1 -1
- package/dist/lib/utils/response-unwrapper.d.ts.map +1 -1
- package/dist/lib/utils/response-unwrapper.js +12 -0
- package/dist/lib/utils/response-unwrapper.js.map +1 -1
- package/dist/lib/utils/union-errors.d.ts +16 -0
- package/dist/lib/utils/union-errors.d.ts.map +1 -0
- package/dist/lib/utils/union-errors.js +34 -0
- package/dist/lib/utils/union-errors.js.map +1 -0
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic Compliance Scenarios
|
|
4
|
+
*
|
|
5
|
+
* These scenarios use the comply_test_controller to force seller-side
|
|
6
|
+
* state transitions, enabling full state machine verification.
|
|
7
|
+
* Only run when the controller is detected.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.testCreativeStateMachine = testCreativeStateMachine;
|
|
11
|
+
exports.testMediaBuyStateMachine = testMediaBuyStateMachine;
|
|
12
|
+
exports.testAccountStateMachine = testAccountStateMachine;
|
|
13
|
+
exports.testSessionStateMachine = testSessionStateMachine;
|
|
14
|
+
exports.testDeliverySimulation = testDeliverySimulation;
|
|
15
|
+
exports.testBudgetSimulation = testBudgetSimulation;
|
|
16
|
+
exports.testControllerValidation = testControllerValidation;
|
|
17
|
+
const client_1 = require("../client");
|
|
18
|
+
const test_controller_1 = require("../test-controller");
|
|
19
|
+
const media_buy_1 = require("./media-buy");
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function getController(options) {
|
|
24
|
+
return options._controllerCapabilities;
|
|
25
|
+
}
|
|
26
|
+
/** Extract an entity ID from step results (created_id, response_preview field, or plural array) */
|
|
27
|
+
function extractIdFromSteps(stepsToSearch, field) {
|
|
28
|
+
const pluralField = field.endsWith('_id') ? field + 's' : undefined; // creative_id → creative_ids
|
|
29
|
+
for (const step of stepsToSearch) {
|
|
30
|
+
if (step.created_id)
|
|
31
|
+
return step.created_id;
|
|
32
|
+
if (step.response_preview) {
|
|
33
|
+
try {
|
|
34
|
+
const preview = JSON.parse(step.response_preview);
|
|
35
|
+
if (preview[field])
|
|
36
|
+
return preview[field];
|
|
37
|
+
if (pluralField && Array.isArray(preview[pluralField]) && preview[pluralField][0]) {
|
|
38
|
+
return preview[pluralField][0];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* skip */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/** Build a TestStepResult from a controller transition response */
|
|
49
|
+
function transitionStep(label, response, expectedSuccess, durationMs) {
|
|
50
|
+
const passed = response.success === expectedSuccess;
|
|
51
|
+
if (response.success) {
|
|
52
|
+
return {
|
|
53
|
+
step: label,
|
|
54
|
+
task: 'comply_test_controller',
|
|
55
|
+
passed,
|
|
56
|
+
duration_ms: durationMs,
|
|
57
|
+
details: `${response.previous_state} → ${response.current_state}`,
|
|
58
|
+
response_preview: JSON.stringify({ previous_state: response.previous_state, current_state: response.current_state }, null, 2),
|
|
59
|
+
...(!passed && {
|
|
60
|
+
error: `Expected failure but got success: ${response.previous_state} → ${response.current_state}`,
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return {
|
|
66
|
+
step: label,
|
|
67
|
+
task: 'comply_test_controller',
|
|
68
|
+
passed,
|
|
69
|
+
duration_ms: durationMs,
|
|
70
|
+
details: `Error: ${response.error} — ${response.error_detail || ''}`,
|
|
71
|
+
response_preview: JSON.stringify({ error: response.error, current_state: response.current_state }, null, 2),
|
|
72
|
+
...(!passed && { error: `Expected success but got ${response.error}: ${response.error_detail || ''}` }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create timed wrappers bound to specific options.
|
|
78
|
+
* This ensures controller calls use the same account as test scenarios.
|
|
79
|
+
*/
|
|
80
|
+
function createTimedHelpers(client, options) {
|
|
81
|
+
return {
|
|
82
|
+
async forceStatus(scenario, params) {
|
|
83
|
+
const start = Date.now();
|
|
84
|
+
const response = await (0, test_controller_1.forceStatus)(client, scenario, params, options);
|
|
85
|
+
return { response, durationMs: Date.now() - start };
|
|
86
|
+
},
|
|
87
|
+
async simulate(scenario, params) {
|
|
88
|
+
const start = Date.now();
|
|
89
|
+
const response = await (0, test_controller_1.simulate)(client, scenario, params, options);
|
|
90
|
+
return { response, durationMs: Date.now() - start };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Call a typed method on the client via executeTask.
|
|
96
|
+
* Uses the same pattern as existing scenarios but through the typed executeTask overload.
|
|
97
|
+
*/
|
|
98
|
+
function callTask(client, taskName, params) {
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- AgentClient.executeTask exists but TestClient type doesn't expose it directly
|
|
100
|
+
return client.executeTask(taskName, params);
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Creative State Machine
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
async function testCreativeStateMachine(agentUrl, options) {
|
|
106
|
+
const steps = [];
|
|
107
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
108
|
+
const controller = getController(options);
|
|
109
|
+
if (!controller?.detected || !(0, test_controller_1.supportsScenario)(controller, 'force_creative_status')) {
|
|
110
|
+
steps.push({
|
|
111
|
+
step: 'Controller check',
|
|
112
|
+
task: 'comply_test_controller',
|
|
113
|
+
passed: true,
|
|
114
|
+
duration_ms: 0,
|
|
115
|
+
details: 'force_creative_status not supported — skipping',
|
|
116
|
+
});
|
|
117
|
+
return { steps };
|
|
118
|
+
}
|
|
119
|
+
const ctrl = createTimedHelpers(client, options);
|
|
120
|
+
// Step 1: Sync a creative to get an entity to work with
|
|
121
|
+
const { steps: syncSteps, profile } = await (0, media_buy_1.testCreativeSync)(agentUrl, options);
|
|
122
|
+
steps.push(...syncSteps);
|
|
123
|
+
const creativeId = extractIdFromSteps(syncSteps, 'creative_id');
|
|
124
|
+
if (!creativeId) {
|
|
125
|
+
steps.push({
|
|
126
|
+
step: 'Find creative for state machine test',
|
|
127
|
+
passed: false,
|
|
128
|
+
duration_ms: 0,
|
|
129
|
+
error: 'No creative_id found from sync_creatives — cannot test state machine',
|
|
130
|
+
});
|
|
131
|
+
return { steps, profile };
|
|
132
|
+
}
|
|
133
|
+
// Step 2: Force to approved
|
|
134
|
+
const { response: approveResult, durationMs: approveDur } = await ctrl.forceStatus('force_creative_status', {
|
|
135
|
+
creative_id: creativeId,
|
|
136
|
+
status: 'approved',
|
|
137
|
+
});
|
|
138
|
+
steps.push(transitionStep('Force creative → approved', approveResult, true, approveDur));
|
|
139
|
+
// Step 3: Verify via list_creatives
|
|
140
|
+
if (approveResult.success && profile?.tools.includes('list_creatives')) {
|
|
141
|
+
const { result: listResult, step: listStep } = await (0, client_1.runStep)('Verify creative status via list_creatives', 'list_creatives', async () => callTask(client, 'list_creatives', {}));
|
|
142
|
+
if (listResult?.success && listResult?.data) {
|
|
143
|
+
const creatives = listResult.data.creatives || [];
|
|
144
|
+
const found = creatives.find((c) => c.creative_id === creativeId);
|
|
145
|
+
if (found) {
|
|
146
|
+
listStep.details = `Creative ${creativeId} status: ${found.status}`;
|
|
147
|
+
if (found.status !== 'approved') {
|
|
148
|
+
listStep.passed = false;
|
|
149
|
+
listStep.error = `Expected status 'approved', got '${found.status}'`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
steps.push(listStep);
|
|
154
|
+
}
|
|
155
|
+
// Step 4: Force to archived
|
|
156
|
+
const { response: archiveResult, durationMs: archiveDur } = await ctrl.forceStatus('force_creative_status', {
|
|
157
|
+
creative_id: creativeId,
|
|
158
|
+
status: 'archived',
|
|
159
|
+
});
|
|
160
|
+
steps.push(transitionStep('Force creative → archived', archiveResult, true, archiveDur));
|
|
161
|
+
// Step 5: Invalid transition — archived is terminal, can't go back to processing
|
|
162
|
+
const { response: invalidResult, durationMs: invalidDur } = await ctrl.forceStatus('force_creative_status', {
|
|
163
|
+
creative_id: creativeId,
|
|
164
|
+
status: 'processing',
|
|
165
|
+
});
|
|
166
|
+
steps.push(transitionStep('Invalid: archived → processing (expect INVALID_TRANSITION)', invalidResult, false, invalidDur));
|
|
167
|
+
if (!invalidResult.success && invalidResult.error !== 'INVALID_TRANSITION') {
|
|
168
|
+
steps.push({
|
|
169
|
+
step: 'Validate error code for invalid transition',
|
|
170
|
+
task: 'comply_test_controller',
|
|
171
|
+
passed: false,
|
|
172
|
+
duration_ms: 0,
|
|
173
|
+
error: `Expected error code INVALID_TRANSITION, got ${invalidResult.error}`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Step 6: Force to rejected with reason on a fresh creative.
|
|
177
|
+
// Sellers may auto-approve on sync, so force to pending_review first (the only state
|
|
178
|
+
// that allows rejection in most state machines), then reject.
|
|
179
|
+
const { steps: resyncSteps } = await (0, media_buy_1.testCreativeSync)(agentUrl, options);
|
|
180
|
+
const freshCreativeId = extractIdFromSteps(resyncSteps, 'creative_id');
|
|
181
|
+
if (freshCreativeId) {
|
|
182
|
+
// Try to reach a state that allows rejection
|
|
183
|
+
const { response: toPendingReview } = await ctrl.forceStatus('force_creative_status', {
|
|
184
|
+
creative_id: freshCreativeId,
|
|
185
|
+
status: 'pending_review',
|
|
186
|
+
});
|
|
187
|
+
// Whether or not pending_review was reachable, try rejecting from wherever we are.
|
|
188
|
+
// Some sellers allow rejection from processing directly; others require pending_review first.
|
|
189
|
+
const { response: rejectResult, durationMs: rejectDur } = await ctrl.forceStatus('force_creative_status', {
|
|
190
|
+
creative_id: freshCreativeId,
|
|
191
|
+
status: 'rejected',
|
|
192
|
+
rejection_reason: 'Brand safety policy violation (comply test)',
|
|
193
|
+
});
|
|
194
|
+
steps.push(transitionStep('Force creative → rejected with reason', rejectResult, true, rejectDur));
|
|
195
|
+
}
|
|
196
|
+
return { steps, profile };
|
|
197
|
+
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Media Buy State Machine
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
async function testMediaBuyStateMachine(agentUrl, options) {
|
|
202
|
+
const steps = [];
|
|
203
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
204
|
+
const controller = getController(options);
|
|
205
|
+
if (!controller?.detected || !(0, test_controller_1.supportsScenario)(controller, 'force_media_buy_status')) {
|
|
206
|
+
steps.push({
|
|
207
|
+
step: 'Controller check',
|
|
208
|
+
task: 'comply_test_controller',
|
|
209
|
+
passed: true,
|
|
210
|
+
duration_ms: 0,
|
|
211
|
+
details: 'force_media_buy_status not supported — skipping',
|
|
212
|
+
});
|
|
213
|
+
return { steps };
|
|
214
|
+
}
|
|
215
|
+
const ctrl = createTimedHelpers(client, options);
|
|
216
|
+
// Create a media buy
|
|
217
|
+
const { steps: createSteps, profile, mediaBuyId } = await (0, media_buy_1.testCreateMediaBuy)(agentUrl, options);
|
|
218
|
+
steps.push(...createSteps);
|
|
219
|
+
if (!mediaBuyId) {
|
|
220
|
+
steps.push({
|
|
221
|
+
step: 'Find media buy for state machine test',
|
|
222
|
+
passed: false,
|
|
223
|
+
duration_ms: 0,
|
|
224
|
+
error: 'No media_buy_id from create_media_buy — cannot test state machine',
|
|
225
|
+
});
|
|
226
|
+
return { steps, profile };
|
|
227
|
+
}
|
|
228
|
+
// Force to active
|
|
229
|
+
const { response: activateResult, durationMs: activateDur } = await ctrl.forceStatus('force_media_buy_status', {
|
|
230
|
+
media_buy_id: mediaBuyId,
|
|
231
|
+
status: 'active',
|
|
232
|
+
});
|
|
233
|
+
steps.push(transitionStep('Force media buy → active', activateResult, true, activateDur));
|
|
234
|
+
// Verify via get_media_buys
|
|
235
|
+
if (activateResult.success && profile?.tools.includes('get_media_buys')) {
|
|
236
|
+
const { result: getResult, step: getStep } = await (0, client_1.runStep)('Verify media buy status via get_media_buys', 'get_media_buys', async () => callTask(client, 'get_media_buys', { media_buy_id: mediaBuyId }));
|
|
237
|
+
if (getResult?.success && getResult?.data) {
|
|
238
|
+
const data = getResult.data;
|
|
239
|
+
const buys = data.media_buys || [data];
|
|
240
|
+
const found = buys.find((b) => b.media_buy_id === mediaBuyId);
|
|
241
|
+
if (found) {
|
|
242
|
+
getStep.details = `Media buy ${mediaBuyId} status: ${found.status}`;
|
|
243
|
+
if (found.status !== 'active') {
|
|
244
|
+
getStep.passed = false;
|
|
245
|
+
getStep.error = `Expected status 'active', got '${found.status}'`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
steps.push(getStep);
|
|
250
|
+
}
|
|
251
|
+
// Force to completed (terminal)
|
|
252
|
+
const { response: completeResult, durationMs: completeDur } = await ctrl.forceStatus('force_media_buy_status', {
|
|
253
|
+
media_buy_id: mediaBuyId,
|
|
254
|
+
status: 'completed',
|
|
255
|
+
});
|
|
256
|
+
steps.push(transitionStep('Force media buy → completed', completeResult, true, completeDur));
|
|
257
|
+
// Invalid: completed → active (terminal)
|
|
258
|
+
const { response: invalidResult, durationMs: invalidDur } = await ctrl.forceStatus('force_media_buy_status', {
|
|
259
|
+
media_buy_id: mediaBuyId,
|
|
260
|
+
status: 'active',
|
|
261
|
+
});
|
|
262
|
+
steps.push(transitionStep('Invalid: completed → active (expect INVALID_TRANSITION)', invalidResult, false, invalidDur));
|
|
263
|
+
// Test rejection: force to pending_activation first (some sellers create as active),
|
|
264
|
+
// then reject from there. If the seller doesn't support pending_activation, skip.
|
|
265
|
+
const { steps: create2Steps, mediaBuyId: mediaBuyId2 } = await (0, media_buy_1.testCreateMediaBuy)(agentUrl, options);
|
|
266
|
+
steps.push(...create2Steps);
|
|
267
|
+
if (mediaBuyId2) {
|
|
268
|
+
// Try to force to pending_activation first
|
|
269
|
+
const { response: pendingResult } = await ctrl.forceStatus('force_media_buy_status', {
|
|
270
|
+
media_buy_id: mediaBuyId2,
|
|
271
|
+
status: 'pending_activation',
|
|
272
|
+
});
|
|
273
|
+
if (pendingResult.success) {
|
|
274
|
+
// Now reject from pending_activation
|
|
275
|
+
const { response: rejectResult, durationMs: rejectDur } = await ctrl.forceStatus('force_media_buy_status', {
|
|
276
|
+
media_buy_id: mediaBuyId2,
|
|
277
|
+
status: 'rejected',
|
|
278
|
+
rejection_reason: 'Policy violation (comply test)',
|
|
279
|
+
});
|
|
280
|
+
steps.push(transitionStep('Force media buy → rejected from pending_activation', rejectResult, true, rejectDur));
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// Can't reach pending_activation — test cancellation instead (valid from active)
|
|
284
|
+
const { response: cancelResult, durationMs: cancelDur } = await ctrl.forceStatus('force_media_buy_status', {
|
|
285
|
+
media_buy_id: mediaBuyId2,
|
|
286
|
+
status: 'canceled',
|
|
287
|
+
});
|
|
288
|
+
steps.push(transitionStep('Force media buy → canceled from active', cancelResult, true, cancelDur));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return { steps, profile };
|
|
292
|
+
}
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// Account State Machine
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
async function testAccountStateMachine(agentUrl, options) {
|
|
297
|
+
const steps = [];
|
|
298
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
299
|
+
const controller = getController(options);
|
|
300
|
+
if (!controller?.detected || !(0, test_controller_1.supportsScenario)(controller, 'force_account_status')) {
|
|
301
|
+
steps.push({
|
|
302
|
+
step: 'Controller check',
|
|
303
|
+
task: 'comply_test_controller',
|
|
304
|
+
passed: true,
|
|
305
|
+
duration_ms: 0,
|
|
306
|
+
details: 'force_account_status not supported — skipping',
|
|
307
|
+
});
|
|
308
|
+
return { steps };
|
|
309
|
+
}
|
|
310
|
+
const ctrl = createTimedHelpers(client, options);
|
|
311
|
+
// Discover an account to work with
|
|
312
|
+
const profile = options._profile;
|
|
313
|
+
let accountId;
|
|
314
|
+
if (profile?.tools.includes('list_accounts')) {
|
|
315
|
+
const { result: listResult, step: listStep } = await (0, client_1.runStep)('List accounts for state machine test', 'list_accounts', async () => callTask(client, 'list_accounts', {}));
|
|
316
|
+
if (listResult?.success && listResult?.data) {
|
|
317
|
+
const accounts = listResult.data.accounts || [];
|
|
318
|
+
const active = accounts.find((a) => a.status === 'active');
|
|
319
|
+
accountId = (active?.account_id || accounts[0]?.account_id);
|
|
320
|
+
listStep.details = `Found ${accounts.length} account(s), using ${accountId || 'none'}`;
|
|
321
|
+
}
|
|
322
|
+
steps.push(listStep);
|
|
323
|
+
}
|
|
324
|
+
if (!accountId) {
|
|
325
|
+
steps.push({
|
|
326
|
+
step: 'Find account for state machine test',
|
|
327
|
+
passed: true,
|
|
328
|
+
duration_ms: 0,
|
|
329
|
+
details: 'No account found — skipping account state machine test',
|
|
330
|
+
});
|
|
331
|
+
return { steps, profile };
|
|
332
|
+
}
|
|
333
|
+
// Force to suspended
|
|
334
|
+
const { response: suspendResult, durationMs: suspendDur } = await ctrl.forceStatus('force_account_status', {
|
|
335
|
+
account_id: accountId,
|
|
336
|
+
status: 'suspended',
|
|
337
|
+
});
|
|
338
|
+
steps.push(transitionStep('Force account → suspended', suspendResult, true, suspendDur));
|
|
339
|
+
// Verify operations are gated: create_media_buy should fail
|
|
340
|
+
if (suspendResult.success && profile?.tools.includes('create_media_buy') && profile?.tools.includes('get_products')) {
|
|
341
|
+
const { result: createResult, step: createStep } = await (0, client_1.runStep)('Verify create_media_buy blocked when suspended', 'create_media_buy', async () => callTask(client, 'create_media_buy', {
|
|
342
|
+
brief: 'comply test — should be blocked by suspension',
|
|
343
|
+
budget: { amount: 100, currency: 'USD' },
|
|
344
|
+
}));
|
|
345
|
+
// We expect this to fail
|
|
346
|
+
if (createResult?.success) {
|
|
347
|
+
createStep.passed = false;
|
|
348
|
+
createStep.error = 'create_media_buy succeeded but account is suspended — operation gate not enforced';
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
createStep.passed = true;
|
|
352
|
+
createStep.details = 'create_media_buy correctly blocked when account is suspended';
|
|
353
|
+
}
|
|
354
|
+
steps.push(createStep);
|
|
355
|
+
}
|
|
356
|
+
// Reactivate
|
|
357
|
+
const { response: reactivateResult, durationMs: reactivateDur } = await ctrl.forceStatus('force_account_status', {
|
|
358
|
+
account_id: accountId,
|
|
359
|
+
status: 'active',
|
|
360
|
+
});
|
|
361
|
+
steps.push(transitionStep('Force account → active (reactivate)', reactivateResult, true, reactivateDur));
|
|
362
|
+
// Force to payment_required
|
|
363
|
+
const { response: paymentResult, durationMs: paymentDur } = await ctrl.forceStatus('force_account_status', {
|
|
364
|
+
account_id: accountId,
|
|
365
|
+
status: 'payment_required',
|
|
366
|
+
});
|
|
367
|
+
steps.push(transitionStep('Force account → payment_required', paymentResult, true, paymentDur));
|
|
368
|
+
// Restore to active
|
|
369
|
+
const { response: restoreResult, durationMs: restoreDur } = await ctrl.forceStatus('force_account_status', {
|
|
370
|
+
account_id: accountId,
|
|
371
|
+
status: 'active',
|
|
372
|
+
});
|
|
373
|
+
steps.push(transitionStep('Force account → active (restore)', restoreResult, true, restoreDur));
|
|
374
|
+
return { steps, profile };
|
|
375
|
+
}
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
// SI Session State Machine
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
async function testSessionStateMachine(agentUrl, options) {
|
|
380
|
+
const steps = [];
|
|
381
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
382
|
+
const controller = getController(options);
|
|
383
|
+
const profile = options._profile;
|
|
384
|
+
if (!controller?.detected || !(0, test_controller_1.supportsScenario)(controller, 'force_session_status')) {
|
|
385
|
+
steps.push({
|
|
386
|
+
step: 'Controller check',
|
|
387
|
+
task: 'comply_test_controller',
|
|
388
|
+
passed: true,
|
|
389
|
+
duration_ms: 0,
|
|
390
|
+
details: 'force_session_status not supported — skipping',
|
|
391
|
+
});
|
|
392
|
+
return { steps };
|
|
393
|
+
}
|
|
394
|
+
if (!profile?.tools.includes('si_initiate_session')) {
|
|
395
|
+
steps.push({
|
|
396
|
+
step: 'SI tools check',
|
|
397
|
+
passed: true,
|
|
398
|
+
duration_ms: 0,
|
|
399
|
+
details: 'si_initiate_session not available — skipping',
|
|
400
|
+
});
|
|
401
|
+
return { steps };
|
|
402
|
+
}
|
|
403
|
+
const ctrl = createTimedHelpers(client, options);
|
|
404
|
+
// Initiate a session
|
|
405
|
+
const { result: initResult, step: initStep } = await (0, client_1.runStep)('Initiate SI session for state machine test', 'si_initiate_session', async () => callTask(client, 'si_initiate_session', {
|
|
406
|
+
identity: { user_type: 'consumer' },
|
|
407
|
+
supported_capabilities: { response_formats: ['text'] },
|
|
408
|
+
}));
|
|
409
|
+
steps.push(initStep);
|
|
410
|
+
const sessionId = initResult?.data?.session_id;
|
|
411
|
+
if (!sessionId) {
|
|
412
|
+
steps.push({
|
|
413
|
+
step: 'Extract session ID',
|
|
414
|
+
passed: false,
|
|
415
|
+
duration_ms: 0,
|
|
416
|
+
error: 'No session_id in initiate response',
|
|
417
|
+
});
|
|
418
|
+
return { steps, profile };
|
|
419
|
+
}
|
|
420
|
+
// Force session timeout
|
|
421
|
+
const { response: timeoutResult, durationMs: timeoutDur } = await ctrl.forceStatus('force_session_status', {
|
|
422
|
+
session_id: sessionId,
|
|
423
|
+
status: 'terminated',
|
|
424
|
+
termination_reason: 'session_timeout',
|
|
425
|
+
});
|
|
426
|
+
steps.push(transitionStep('Force session → terminated (timeout)', timeoutResult, true, timeoutDur));
|
|
427
|
+
// Verify: si_send_message should fail with SESSION_NOT_FOUND or similar
|
|
428
|
+
if (timeoutResult.success && profile.tools.includes('si_send_message')) {
|
|
429
|
+
const { result: msgResult, step: msgStep } = await (0, client_1.runStep)('Verify si_send_message fails after forced termination', 'si_send_message', async () => callTask(client, 'si_send_message', {
|
|
430
|
+
session_id: sessionId,
|
|
431
|
+
message: 'comply test — session should be terminated',
|
|
432
|
+
}));
|
|
433
|
+
if (msgResult?.success) {
|
|
434
|
+
const data = msgResult.data;
|
|
435
|
+
const errors = data?.errors;
|
|
436
|
+
if ((errors && errors.length > 0) || data?.session_status === 'terminated') {
|
|
437
|
+
msgStep.passed = true;
|
|
438
|
+
msgStep.details = 'Agent correctly returned error/terminated status for expired session';
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
msgStep.passed = false;
|
|
442
|
+
msgStep.error = 'si_send_message succeeded on terminated session — session state not enforced';
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
msgStep.passed = true;
|
|
447
|
+
msgStep.details = 'si_send_message correctly failed on terminated session';
|
|
448
|
+
}
|
|
449
|
+
steps.push(msgStep);
|
|
450
|
+
}
|
|
451
|
+
return { steps, profile };
|
|
452
|
+
}
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
// Delivery Simulation
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
async function testDeliverySimulation(agentUrl, options) {
|
|
457
|
+
const steps = [];
|
|
458
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
459
|
+
const controller = getController(options);
|
|
460
|
+
const profile = options._profile;
|
|
461
|
+
if (!controller?.detected || !(0, test_controller_1.supportsScenario)(controller, 'simulate_delivery')) {
|
|
462
|
+
steps.push({
|
|
463
|
+
step: 'Controller check',
|
|
464
|
+
task: 'comply_test_controller',
|
|
465
|
+
passed: true,
|
|
466
|
+
duration_ms: 0,
|
|
467
|
+
details: 'simulate_delivery not supported — skipping',
|
|
468
|
+
});
|
|
469
|
+
return { steps };
|
|
470
|
+
}
|
|
471
|
+
const ctrl = createTimedHelpers(client, options);
|
|
472
|
+
// Create a media buy to simulate delivery on
|
|
473
|
+
const { steps: createSteps, mediaBuyId } = await (0, media_buy_1.testCreateMediaBuy)(agentUrl, options);
|
|
474
|
+
steps.push(...createSteps);
|
|
475
|
+
if (!mediaBuyId) {
|
|
476
|
+
steps.push({
|
|
477
|
+
step: 'Find media buy for delivery simulation',
|
|
478
|
+
passed: false,
|
|
479
|
+
duration_ms: 0,
|
|
480
|
+
error: 'No media_buy_id — cannot test delivery simulation',
|
|
481
|
+
});
|
|
482
|
+
return { steps, profile };
|
|
483
|
+
}
|
|
484
|
+
// Simulate delivery
|
|
485
|
+
const { response: simResult, durationMs: simDur } = await ctrl.simulate('simulate_delivery', {
|
|
486
|
+
media_buy_id: mediaBuyId,
|
|
487
|
+
impressions: 10000,
|
|
488
|
+
clicks: 150,
|
|
489
|
+
reported_spend: { amount: 150.0, currency: 'USD' },
|
|
490
|
+
});
|
|
491
|
+
if (simResult.success) {
|
|
492
|
+
steps.push({
|
|
493
|
+
step: 'Simulate delivery data',
|
|
494
|
+
task: 'comply_test_controller',
|
|
495
|
+
passed: true,
|
|
496
|
+
duration_ms: simDur,
|
|
497
|
+
details: simResult.message || 'Delivery simulated',
|
|
498
|
+
response_preview: JSON.stringify(simResult.simulated, null, 2),
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
steps.push({
|
|
503
|
+
step: 'Simulate delivery data',
|
|
504
|
+
task: 'comply_test_controller',
|
|
505
|
+
passed: false,
|
|
506
|
+
duration_ms: simDur,
|
|
507
|
+
error: `${simResult.error}: ${simResult.error_detail || ''}`,
|
|
508
|
+
});
|
|
509
|
+
return { steps, profile };
|
|
510
|
+
}
|
|
511
|
+
// Verify via get_media_buy_delivery
|
|
512
|
+
if (profile?.tools.includes('get_media_buy_delivery')) {
|
|
513
|
+
const { result: deliveryResult, step: deliveryStep } = await (0, client_1.runStep)('Verify delivery data via get_media_buy_delivery', 'get_media_buy_delivery', async () => callTask(client, 'get_media_buy_delivery', { media_buy_id: mediaBuyId, account: (0, client_1.resolveAccount)(options) }));
|
|
514
|
+
if (deliveryResult?.success && deliveryResult?.data) {
|
|
515
|
+
const data = deliveryResult.data;
|
|
516
|
+
// Handle multiple response shapes:
|
|
517
|
+
// - { media_buy_deliveries: [{ totals: { impressions } }] } (training agent)
|
|
518
|
+
// - { summary: { impressions } } or { impressions } (other agents)
|
|
519
|
+
const deliveries = data.media_buy_deliveries;
|
|
520
|
+
const totals = deliveries?.[0]?.totals;
|
|
521
|
+
const summary = data.summary;
|
|
522
|
+
const impressions = (totals?.impressions ??
|
|
523
|
+
data.impressions ??
|
|
524
|
+
data.total_impressions ??
|
|
525
|
+
summary?.impressions);
|
|
526
|
+
if (impressions !== undefined && impressions >= 10000) {
|
|
527
|
+
deliveryStep.details = `Delivery reflects simulated data: ${impressions} impressions`;
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
deliveryStep.passed = false;
|
|
531
|
+
deliveryStep.error = `Expected ≥10000 impressions in delivery report, got ${impressions}`;
|
|
532
|
+
}
|
|
533
|
+
deliveryStep.response_preview = JSON.stringify({ impressions, raw_keys: Object.keys(data) }, null, 2);
|
|
534
|
+
}
|
|
535
|
+
steps.push(deliveryStep);
|
|
536
|
+
}
|
|
537
|
+
return { steps, profile };
|
|
538
|
+
}
|
|
539
|
+
// ---------------------------------------------------------------------------
|
|
540
|
+
// Budget Simulation
|
|
541
|
+
// ---------------------------------------------------------------------------
|
|
542
|
+
async function testBudgetSimulation(agentUrl, options) {
|
|
543
|
+
const steps = [];
|
|
544
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
545
|
+
const controller = getController(options);
|
|
546
|
+
const profile = options._profile;
|
|
547
|
+
if (!controller?.detected || !(0, test_controller_1.supportsScenario)(controller, 'simulate_budget_spend')) {
|
|
548
|
+
steps.push({
|
|
549
|
+
step: 'Controller check',
|
|
550
|
+
task: 'comply_test_controller',
|
|
551
|
+
passed: true,
|
|
552
|
+
duration_ms: 0,
|
|
553
|
+
details: 'simulate_budget_spend not supported — skipping',
|
|
554
|
+
});
|
|
555
|
+
return { steps };
|
|
556
|
+
}
|
|
557
|
+
const ctrl = createTimedHelpers(client, options);
|
|
558
|
+
// Create a media buy with a known budget
|
|
559
|
+
const { steps: createSteps, mediaBuyId } = await (0, media_buy_1.testCreateMediaBuy)(agentUrl, options);
|
|
560
|
+
steps.push(...createSteps);
|
|
561
|
+
if (!mediaBuyId) {
|
|
562
|
+
steps.push({
|
|
563
|
+
step: 'Find media buy for budget simulation',
|
|
564
|
+
passed: false,
|
|
565
|
+
duration_ms: 0,
|
|
566
|
+
error: 'No media_buy_id — cannot test budget simulation',
|
|
567
|
+
});
|
|
568
|
+
return { steps, profile };
|
|
569
|
+
}
|
|
570
|
+
// Simulate 95% spend
|
|
571
|
+
const { response: simResult, durationMs: simDur } = await ctrl.simulate('simulate_budget_spend', {
|
|
572
|
+
media_buy_id: mediaBuyId,
|
|
573
|
+
spend_percentage: 95,
|
|
574
|
+
});
|
|
575
|
+
if (simResult.success) {
|
|
576
|
+
steps.push({
|
|
577
|
+
step: 'Simulate budget spend to 95%',
|
|
578
|
+
task: 'comply_test_controller',
|
|
579
|
+
passed: true,
|
|
580
|
+
duration_ms: simDur,
|
|
581
|
+
details: simResult.message || 'Budget spend simulated to 95%',
|
|
582
|
+
response_preview: JSON.stringify(simResult.simulated, null, 2),
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
steps.push({
|
|
587
|
+
step: 'Simulate budget spend to 95%',
|
|
588
|
+
task: 'comply_test_controller',
|
|
589
|
+
passed: false,
|
|
590
|
+
duration_ms: simDur,
|
|
591
|
+
error: `${simResult.error}: ${simResult.error_detail || ''}`,
|
|
592
|
+
});
|
|
593
|
+
return { steps, profile };
|
|
594
|
+
}
|
|
595
|
+
// Simulate 100% spend
|
|
596
|
+
const { response: depletedResult, durationMs: depletedDur } = await ctrl.simulate('simulate_budget_spend', {
|
|
597
|
+
media_buy_id: mediaBuyId,
|
|
598
|
+
spend_percentage: 100,
|
|
599
|
+
});
|
|
600
|
+
if (depletedResult.success) {
|
|
601
|
+
steps.push({
|
|
602
|
+
step: 'Simulate budget fully depleted (100%)',
|
|
603
|
+
task: 'comply_test_controller',
|
|
604
|
+
passed: true,
|
|
605
|
+
duration_ms: depletedDur,
|
|
606
|
+
details: depletedResult.message || 'Budget fully depleted',
|
|
607
|
+
response_preview: JSON.stringify(depletedResult.simulated, null, 2),
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
steps.push({
|
|
612
|
+
step: 'Simulate budget fully depleted (100%)',
|
|
613
|
+
task: 'comply_test_controller',
|
|
614
|
+
passed: false,
|
|
615
|
+
duration_ms: depletedDur,
|
|
616
|
+
error: `${depletedResult.error}: ${depletedResult.error_detail || ''}`,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
return { steps, profile };
|
|
620
|
+
}
|
|
621
|
+
// ---------------------------------------------------------------------------
|
|
622
|
+
// Controller Self-Validation
|
|
623
|
+
// ---------------------------------------------------------------------------
|
|
624
|
+
async function testControllerValidation(agentUrl, options) {
|
|
625
|
+
const steps = [];
|
|
626
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
627
|
+
const controller = getController(options);
|
|
628
|
+
if (!controller?.detected) {
|
|
629
|
+
steps.push({
|
|
630
|
+
step: 'Controller check',
|
|
631
|
+
task: 'comply_test_controller',
|
|
632
|
+
passed: true,
|
|
633
|
+
duration_ms: 0,
|
|
634
|
+
details: 'No test controller — skipping validation',
|
|
635
|
+
});
|
|
636
|
+
return { steps };
|
|
637
|
+
}
|
|
638
|
+
// Test 1: Unknown scenario → expect UNKNOWN_SCENARIO
|
|
639
|
+
const { result: unknownResult, step: unknownStep } = await (0, client_1.runStep)('Unknown scenario returns UNKNOWN_SCENARIO', 'comply_test_controller', async () => (0, test_controller_1.callControllerRaw)(client, {
|
|
640
|
+
scenario: 'nonexistent_scenario',
|
|
641
|
+
params: {},
|
|
642
|
+
}, options));
|
|
643
|
+
const unknownData = unknownResult?.data;
|
|
644
|
+
if (unknownData && !unknownData.success && unknownData.error === 'UNKNOWN_SCENARIO') {
|
|
645
|
+
unknownStep.details = 'Correctly returned UNKNOWN_SCENARIO for nonexistent_scenario';
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
unknownStep.passed = false;
|
|
649
|
+
unknownStep.error = `Expected UNKNOWN_SCENARIO error, got: ${JSON.stringify(unknownData)}`;
|
|
650
|
+
}
|
|
651
|
+
steps.push(unknownStep);
|
|
652
|
+
// Test 2: Missing required params → expect INVALID_PARAMS
|
|
653
|
+
if ((0, test_controller_1.supportsScenario)(controller, 'force_creative_status')) {
|
|
654
|
+
const start = Date.now();
|
|
655
|
+
const missingResult = await (0, test_controller_1.forceStatus)(client, 'force_creative_status', {}, options);
|
|
656
|
+
const dur = Date.now() - start;
|
|
657
|
+
if (!missingResult.success && missingResult.error === 'INVALID_PARAMS') {
|
|
658
|
+
steps.push({
|
|
659
|
+
step: 'Missing params returns INVALID_PARAMS',
|
|
660
|
+
task: 'comply_test_controller',
|
|
661
|
+
passed: true,
|
|
662
|
+
duration_ms: dur,
|
|
663
|
+
details: 'Correctly returned INVALID_PARAMS for missing creative_id/status',
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
steps.push({
|
|
668
|
+
step: 'Missing params returns INVALID_PARAMS',
|
|
669
|
+
task: 'comply_test_controller',
|
|
670
|
+
passed: false,
|
|
671
|
+
duration_ms: dur,
|
|
672
|
+
error: `Expected INVALID_PARAMS, got: ${!missingResult.success ? missingResult.error : 'success'}`,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Test 3: NOT_FOUND for nonexistent entity
|
|
677
|
+
if ((0, test_controller_1.supportsScenario)(controller, 'force_creative_status')) {
|
|
678
|
+
const start = Date.now();
|
|
679
|
+
const notFoundResult = await (0, test_controller_1.forceStatus)(client, 'force_creative_status', {
|
|
680
|
+
creative_id: 'comply-test-nonexistent-000000000000',
|
|
681
|
+
status: 'approved',
|
|
682
|
+
}, options);
|
|
683
|
+
const dur = Date.now() - start;
|
|
684
|
+
if (!notFoundResult.success && notFoundResult.error === 'NOT_FOUND') {
|
|
685
|
+
steps.push({
|
|
686
|
+
step: 'Nonexistent entity returns NOT_FOUND',
|
|
687
|
+
task: 'comply_test_controller',
|
|
688
|
+
passed: true,
|
|
689
|
+
duration_ms: dur,
|
|
690
|
+
details: 'Correctly returned NOT_FOUND for nonexistent creative',
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
steps.push({
|
|
695
|
+
step: 'Nonexistent entity returns NOT_FOUND',
|
|
696
|
+
task: 'comply_test_controller',
|
|
697
|
+
passed: false,
|
|
698
|
+
duration_ms: dur,
|
|
699
|
+
error: `Expected NOT_FOUND, got: ${!notFoundResult.success ? notFoundResult.error : 'success'}`,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return { steps };
|
|
704
|
+
}
|
|
705
|
+
//# sourceMappingURL=deterministic.js.map
|