@adcp/client 4.13.0 → 4.15.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 +25 -23
- 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/core/AgentClient.d.ts.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 +6 -3
- 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 +43 -10
- 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/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 +28 -1
- package/dist/lib/testing/agent-tester.js.map +1 -1
- package/dist/lib/testing/client.d.ts +19 -0
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +64 -3
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +218 -3
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/types.d.ts +1 -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 +9 -2
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/orchestrator.d.ts.map +1 -1
- package/dist/lib/testing/orchestrator.js +14 -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/discovery.js +2 -2
- package/dist/lib/testing/scenarios/discovery.js.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.js +11 -11
- 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 +6 -7
- 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 -2
- package/dist/lib/testing/scenarios/index.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/index.js +5 -1
- package/dist/lib/testing/scenarios/index.js.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.d.ts +24 -0
- package/dist/lib/testing/scenarios/media-buy.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.js +555 -8
- package/dist/lib/testing/scenarios/media-buy.js.map +1 -1
- package/dist/lib/testing/scenarios/schema-compliance.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/schema-compliance.js +5 -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 +39 -3
- package/dist/lib/testing/scenarios/signals.js.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.js +6 -6
- 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/types.d.ts +5 -1
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/types/core.generated.d.ts +7890 -92
- 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 +7649 -3768
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +677 -418
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/types/tools.generated.d.ts +8325 -450
- package/dist/lib/types/tools.generated.d.ts.map +1 -1
- package/dist/lib/utils/response-schemas.d.ts +9 -0
- package/dist/lib/utils/response-schemas.d.ts.map +1 -0
- package/dist/lib/utils/response-schemas.js +60 -0
- package/dist/lib/utils/response-schemas.js.map +1 -0
- package/dist/lib/utils/response-unwrapper.d.ts +2 -2
- package/dist/lib/utils/response-unwrapper.d.ts.map +1 -1
- package/dist/lib/utils/response-unwrapper.js +3 -91
- package/dist/lib/utils/response-unwrapper.js.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +1 -1
|
@@ -19,6 +19,9 @@ exports.testCreativeInline = testCreativeInline;
|
|
|
19
19
|
exports.testCreativeReference = testCreativeReference;
|
|
20
20
|
exports.resolveAccountForAudiences = resolveAccountForAudiences;
|
|
21
21
|
exports.testSyncAudiences = testSyncAudiences;
|
|
22
|
+
exports.testMediaBuyLifecycle = testMediaBuyLifecycle;
|
|
23
|
+
exports.testTerminalStateEnforcement = testTerminalStateEnforcement;
|
|
24
|
+
exports.testPackageLifecycle = testPackageLifecycle;
|
|
22
25
|
const client_1 = require("../client");
|
|
23
26
|
const discovery_1 = require("./discovery");
|
|
24
27
|
/**
|
|
@@ -145,7 +148,7 @@ function buildSyncCreativeFromManifest(manifest, fallbackFormatId) {
|
|
|
145
148
|
*/
|
|
146
149
|
async function testCreateMediaBuy(agentUrl, options) {
|
|
147
150
|
const steps = [];
|
|
148
|
-
const client = (0, client_1.
|
|
151
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
149
152
|
// First run discovery
|
|
150
153
|
const { steps: discoverySteps, profile } = await (0, discovery_1.testDiscovery)(agentUrl, options);
|
|
151
154
|
steps.push(...discoverySteps);
|
|
@@ -207,6 +210,7 @@ async function testCreateMediaBuy(agentUrl, options) {
|
|
|
207
210
|
async () => client.createMediaBuy(createRequest));
|
|
208
211
|
let mediaBuyId;
|
|
209
212
|
if (createResult?.success && createResult?.data) {
|
|
213
|
+
steps.push((0, client_1.validateResponseSchema)('create_media_buy', createResult.data));
|
|
210
214
|
const mediaBuy = createResult.data;
|
|
211
215
|
const nested = mediaBuy.media_buy;
|
|
212
216
|
mediaBuyId = (mediaBuy.media_buy_id || nested?.media_buy_id);
|
|
@@ -214,9 +218,15 @@ async function testCreateMediaBuy(agentUrl, options) {
|
|
|
214
218
|
const packages = (mediaBuy.packages || nested?.packages);
|
|
215
219
|
createStep.details = `Created media buy: ${mediaBuyId}, status: ${status}`;
|
|
216
220
|
createStep.created_id = mediaBuyId;
|
|
221
|
+
const confirmedAt = (mediaBuy.confirmed_at ?? nested?.confirmed_at);
|
|
222
|
+
const revision = (mediaBuy.revision ?? nested?.revision);
|
|
223
|
+
const validActions = (mediaBuy.valid_actions ?? nested?.valid_actions);
|
|
217
224
|
createStep.response_preview = JSON.stringify({
|
|
218
225
|
media_buy_id: mediaBuyId,
|
|
219
226
|
status,
|
|
227
|
+
confirmed_at: confirmedAt,
|
|
228
|
+
revision,
|
|
229
|
+
valid_actions: validActions,
|
|
220
230
|
packages_count: packages?.length,
|
|
221
231
|
pricing_model: pricingOption.pricing_model,
|
|
222
232
|
product_name: product.name,
|
|
@@ -235,7 +245,7 @@ async function testCreateMediaBuy(agentUrl, options) {
|
|
|
235
245
|
*/
|
|
236
246
|
async function testFullSalesFlow(agentUrl, options) {
|
|
237
247
|
const steps = [];
|
|
238
|
-
const client = (0, client_1.
|
|
248
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
239
249
|
// Run create media buy flow first
|
|
240
250
|
const { steps: createSteps, profile, mediaBuyId } = await testCreateMediaBuy(agentUrl, options);
|
|
241
251
|
steps.push(...createSteps);
|
|
@@ -276,6 +286,7 @@ async function testFullSalesFlow(agentUrl, options) {
|
|
|
276
286
|
include_snapshot: true,
|
|
277
287
|
}));
|
|
278
288
|
if (snapshotResult?.success && snapshotResult?.data) {
|
|
289
|
+
steps.push((0, client_1.validateResponseSchema)('get_media_buys', snapshotResult.data));
|
|
279
290
|
const mediaBuys = snapshotResult.data.media_buys || [];
|
|
280
291
|
const mediaBuy = mediaBuys.find((item) => item.media_buy_id === mediaBuyId) || mediaBuys[0];
|
|
281
292
|
const packages = mediaBuy?.packages || [];
|
|
@@ -341,9 +352,9 @@ async function testFullSalesFlow(agentUrl, options) {
|
|
|
341
352
|
*/
|
|
342
353
|
async function testCreativeSync(agentUrl, options) {
|
|
343
354
|
const steps = [];
|
|
344
|
-
const client = (0, client_1.
|
|
355
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
345
356
|
// Discover profile
|
|
346
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
357
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
347
358
|
steps.push(profileStep);
|
|
348
359
|
if (!profile.tools.includes('sync_creatives')) {
|
|
349
360
|
steps.push({
|
|
@@ -457,7 +468,7 @@ async function testCreativeSync(agentUrl, options) {
|
|
|
457
468
|
*/
|
|
458
469
|
async function testCreativeInline(agentUrl, options) {
|
|
459
470
|
const steps = [];
|
|
460
|
-
const client = (0, client_1.
|
|
471
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
461
472
|
// Discovery first
|
|
462
473
|
const { steps: discoverySteps, profile } = await (0, discovery_1.testDiscovery)(agentUrl, options);
|
|
463
474
|
steps.push(...discoverySteps);
|
|
@@ -569,6 +580,7 @@ async function testCreativeInline(agentUrl, options) {
|
|
|
569
580
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
570
581
|
async () => client.createMediaBuy(createRequest));
|
|
571
582
|
if (createResult?.success && createResult?.data) {
|
|
583
|
+
steps.push((0, client_1.validateResponseSchema)('create_media_buy', createResult.data));
|
|
572
584
|
const mediaBuy = createResult.data;
|
|
573
585
|
const nested = mediaBuy.media_buy;
|
|
574
586
|
const mediaBuyId = (mediaBuy.media_buy_id || nested?.media_buy_id);
|
|
@@ -601,7 +613,7 @@ async function testCreativeInline(agentUrl, options) {
|
|
|
601
613
|
*/
|
|
602
614
|
async function testCreativeReference(agentUrl, options) {
|
|
603
615
|
const steps = [];
|
|
604
|
-
const client = (0, client_1.
|
|
616
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
605
617
|
const { steps: discoverySteps, profile } = await (0, discovery_1.testDiscovery)(agentUrl, options);
|
|
606
618
|
steps.push(...discoverySteps);
|
|
607
619
|
if (!profile?.tools.includes('build_creative') || !profile.tools.includes('sync_creatives')) {
|
|
@@ -701,6 +713,7 @@ async function testCreativeReference(agentUrl, options) {
|
|
|
701
713
|
});
|
|
702
714
|
const { result: createResult, step: createStep } = await (0, client_1.runStep)('Create media buy with referenced creative', 'create_media_buy', async () => client.executeTask('create_media_buy', createRequest));
|
|
703
715
|
if (createResult?.success && createResult?.data) {
|
|
716
|
+
steps.push((0, client_1.validateResponseSchema)('create_media_buy', createResult.data));
|
|
704
717
|
const mediaBuy = createResult.data;
|
|
705
718
|
const mediaBuyId = mediaBuy.media_buy_id || mediaBuy.media_buy?.media_buy_id;
|
|
706
719
|
const packages = mediaBuy.packages || mediaBuy.media_buy?.packages;
|
|
@@ -790,9 +803,9 @@ async function resolveAccountForAudiences(options, tools, listAccounts) {
|
|
|
790
803
|
*/
|
|
791
804
|
async function testSyncAudiences(agentUrl, options) {
|
|
792
805
|
const steps = [];
|
|
793
|
-
const client = (0, client_1.
|
|
806
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
794
807
|
// Discover agent profile
|
|
795
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
808
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
796
809
|
steps.push(profileStep);
|
|
797
810
|
if (!profileStep.passed) {
|
|
798
811
|
return { steps, profile };
|
|
@@ -867,7 +880,19 @@ async function testSyncAudiences(agentUrl, options) {
|
|
|
867
880
|
action: testAudience?.action,
|
|
868
881
|
status: testAudience?.status,
|
|
869
882
|
uploaded_count: testAudience?.uploaded_count,
|
|
883
|
+
matched_count: testAudience?.matched_count,
|
|
884
|
+
effective_match_rate: testAudience?.effective_match_rate,
|
|
885
|
+
match_breakdown: testAudience?.match_breakdown,
|
|
870
886
|
}, null, 2);
|
|
887
|
+
// Advisory: report match breakdown availability
|
|
888
|
+
if (testAudience?.status === 'ready') {
|
|
889
|
+
if (testAudience.match_breakdown) {
|
|
890
|
+
createStep.details += `, match_breakdown: ${testAudience.match_breakdown.length} ID type(s)`;
|
|
891
|
+
}
|
|
892
|
+
if (testAudience.effective_match_rate != null) {
|
|
893
|
+
createStep.details += `, effective_match_rate: ${(testAudience.effective_match_rate * 100).toFixed(1)}%`;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
871
896
|
}
|
|
872
897
|
else if (createResult && !createResult.success) {
|
|
873
898
|
createStep.passed = false;
|
|
@@ -905,4 +930,526 @@ async function testSyncAudiences(agentUrl, options) {
|
|
|
905
930
|
steps.push(deleteStep);
|
|
906
931
|
return { steps, profile };
|
|
907
932
|
}
|
|
933
|
+
// ---------------------------------------------------------------------------
|
|
934
|
+
// State Machine Compliance Scenarios
|
|
935
|
+
// ---------------------------------------------------------------------------
|
|
936
|
+
/**
|
|
937
|
+
* Extract media buy status from a response, handling nested shapes.
|
|
938
|
+
*/
|
|
939
|
+
function extractStatus(data) {
|
|
940
|
+
const nested = data.media_buy;
|
|
941
|
+
return (data.status ?? nested?.status);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Test: Media Buy Lifecycle
|
|
945
|
+
* Exercises the full state machine: create -> pause -> resume -> get status -> cancel
|
|
946
|
+
*/
|
|
947
|
+
async function testMediaBuyLifecycle(agentUrl, options) {
|
|
948
|
+
const steps = [];
|
|
949
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
950
|
+
// Create a media buy to work with
|
|
951
|
+
const { steps: createSteps, profile, mediaBuyId } = await testCreateMediaBuy(agentUrl, options);
|
|
952
|
+
steps.push(...createSteps);
|
|
953
|
+
if (!mediaBuyId || !profile?.tools.includes('update_media_buy')) {
|
|
954
|
+
return { steps, profile };
|
|
955
|
+
}
|
|
956
|
+
// Track revisions across steps for monotonicity check
|
|
957
|
+
const revisions = [];
|
|
958
|
+
// Step 1: Pause the media buy
|
|
959
|
+
const { result: pauseResult, step: pauseStep } = await (0, client_1.runStep)('Pause media buy', 'update_media_buy', async () => client.updateMediaBuy({
|
|
960
|
+
media_buy_id: mediaBuyId,
|
|
961
|
+
paused: true,
|
|
962
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
963
|
+
}));
|
|
964
|
+
if (pauseResult?.success && pauseResult?.data) {
|
|
965
|
+
const data = pauseResult.data;
|
|
966
|
+
const status = extractStatus(data);
|
|
967
|
+
const pauseRevision = data.revision;
|
|
968
|
+
if (pauseRevision !== undefined)
|
|
969
|
+
revisions.push({ step: 'pause', revision: pauseRevision });
|
|
970
|
+
pauseStep.details = `Paused media buy, status: ${status}`;
|
|
971
|
+
pauseStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, status, revision: pauseRevision }, null, 2);
|
|
972
|
+
if (status && status !== 'paused') {
|
|
973
|
+
pauseStep.warnings = [`Expected status 'paused', got '${status}'`];
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
else if (pauseResult && !pauseResult.success) {
|
|
977
|
+
pauseStep.passed = false;
|
|
978
|
+
pauseStep.error = pauseResult.error || 'Pause operation failed';
|
|
979
|
+
}
|
|
980
|
+
steps.push(pauseStep);
|
|
981
|
+
// Step 2: Resume the media buy
|
|
982
|
+
const { result: resumeResult, step: resumeStep } = await (0, client_1.runStep)('Resume media buy', 'update_media_buy', async () => client.updateMediaBuy({
|
|
983
|
+
media_buy_id: mediaBuyId,
|
|
984
|
+
paused: false,
|
|
985
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
986
|
+
}));
|
|
987
|
+
if (resumeResult?.success && resumeResult?.data) {
|
|
988
|
+
const data = resumeResult.data;
|
|
989
|
+
const status = extractStatus(data);
|
|
990
|
+
const resumeRevision = data.revision;
|
|
991
|
+
if (resumeRevision !== undefined)
|
|
992
|
+
revisions.push({ step: 'resume', revision: resumeRevision });
|
|
993
|
+
resumeStep.details = `Resumed media buy, status: ${status}`;
|
|
994
|
+
resumeStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, status, revision: resumeRevision }, null, 2);
|
|
995
|
+
if (status && status !== 'active' && status !== 'pending_activation') {
|
|
996
|
+
resumeStep.warnings = [`Expected status 'active' or 'pending_activation', got '${status}'`];
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
else if (resumeResult && !resumeResult.success) {
|
|
1000
|
+
resumeStep.passed = false;
|
|
1001
|
+
resumeStep.error = resumeResult.error || 'Resume operation failed';
|
|
1002
|
+
}
|
|
1003
|
+
steps.push(resumeStep);
|
|
1004
|
+
// Step 2b: Budget update — verify substantive field mutation
|
|
1005
|
+
// Find a package to update budget on
|
|
1006
|
+
let budgetPackageId;
|
|
1007
|
+
let originalBudget;
|
|
1008
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1009
|
+
const { result: fetchResult } = await (0, client_1.runStep)('Fetch packages for budget test', 'get_media_buys', async () => client.executeTask('get_media_buys', { media_buy_ids: [mediaBuyId] }));
|
|
1010
|
+
if (fetchResult?.success && fetchResult?.data) {
|
|
1011
|
+
const mbs = (fetchResult.data.media_buys || []);
|
|
1012
|
+
const mb = mbs.find((item) => item.media_buy_id === mediaBuyId) || mbs[0];
|
|
1013
|
+
const pkgs = (mb?.packages || []);
|
|
1014
|
+
if (pkgs[0]) {
|
|
1015
|
+
budgetPackageId = pkgs[0].package_id;
|
|
1016
|
+
originalBudget = pkgs[0].budget;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (budgetPackageId && originalBudget !== undefined) {
|
|
1021
|
+
const newBudget = Math.round(originalBudget * 1.2 * 100) / 100; // 20% increase
|
|
1022
|
+
const { result: budgetResult, step: budgetStep } = await (0, client_1.runStep)('Update package budget', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1023
|
+
media_buy_id: mediaBuyId,
|
|
1024
|
+
packages: [{ package_id: budgetPackageId, budget: newBudget }],
|
|
1025
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1026
|
+
}));
|
|
1027
|
+
if (budgetResult?.success && budgetResult?.data) {
|
|
1028
|
+
const data = budgetResult.data;
|
|
1029
|
+
const budgetRevision = data.revision;
|
|
1030
|
+
if (budgetRevision !== undefined)
|
|
1031
|
+
revisions.push({ step: 'budget_update', revision: budgetRevision });
|
|
1032
|
+
budgetStep.details = `Updated budget from $${originalBudget} to $${newBudget}`;
|
|
1033
|
+
budgetStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, package_id: budgetPackageId, new_budget: newBudget, revision: budgetRevision }, null, 2);
|
|
1034
|
+
}
|
|
1035
|
+
else if (budgetResult && !budgetResult.success) {
|
|
1036
|
+
const error = budgetResult.error || '';
|
|
1037
|
+
if (error.includes('BUDGET_EXCEEDED') || error.includes('budget_exceeded')) {
|
|
1038
|
+
budgetStep.passed = true;
|
|
1039
|
+
budgetStep.details = `Agent rejected budget increase with BUDGET_EXCEEDED — acceptable`;
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
budgetStep.passed = false;
|
|
1043
|
+
budgetStep.error = budgetResult.error || 'Budget update failed';
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
steps.push(budgetStep);
|
|
1047
|
+
}
|
|
1048
|
+
// Step 3: Get status and check valid_actions, confirmed_at, revision, history (if get_media_buys available)
|
|
1049
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1050
|
+
const { result: statusResult, step: statusStep } = await (0, client_1.runStep)('Get media buy status and valid_actions', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
1051
|
+
media_buy_ids: [mediaBuyId],
|
|
1052
|
+
include_history: 10,
|
|
1053
|
+
}));
|
|
1054
|
+
if (statusResult?.success && statusResult?.data) {
|
|
1055
|
+
const mediaBuys = (statusResult.data.media_buys || []);
|
|
1056
|
+
const mediaBuy = mediaBuys.find((item) => item.media_buy_id === mediaBuyId) || mediaBuys[0];
|
|
1057
|
+
if (!mediaBuy) {
|
|
1058
|
+
statusStep.passed = false;
|
|
1059
|
+
statusStep.error = 'get_media_buys did not return the created media buy';
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
const validActions = mediaBuy.valid_actions;
|
|
1063
|
+
const mbRevision = mediaBuy.revision;
|
|
1064
|
+
const mbConfirmedAt = mediaBuy.confirmed_at;
|
|
1065
|
+
const history = mediaBuy.history;
|
|
1066
|
+
const packages = (mediaBuy.packages || []);
|
|
1067
|
+
const hasCreativeDeadline = packages.some(p => p.creative_deadline) || !!mediaBuy.creative_deadline;
|
|
1068
|
+
// Validate history entry shape if present
|
|
1069
|
+
let historyValid = true;
|
|
1070
|
+
if (history?.length) {
|
|
1071
|
+
const missingTimestamp = history.some(h => !h.timestamp);
|
|
1072
|
+
const missingAction = history.some(h => !h.action);
|
|
1073
|
+
if (missingTimestamp || missingAction)
|
|
1074
|
+
historyValid = false;
|
|
1075
|
+
}
|
|
1076
|
+
statusStep.details = `Status: ${mediaBuy.status}, valid_actions: ${validActions ? validActions.join(', ') : 'not provided'}, revision: ${mbRevision ?? 'not provided'}`;
|
|
1077
|
+
statusStep.response_preview = JSON.stringify({
|
|
1078
|
+
media_buy_id: mediaBuy.media_buy_id,
|
|
1079
|
+
status: mediaBuy.status,
|
|
1080
|
+
confirmed_at: mbConfirmedAt,
|
|
1081
|
+
revision: mbRevision,
|
|
1082
|
+
valid_actions: validActions,
|
|
1083
|
+
history_entries: history?.length ?? 0,
|
|
1084
|
+
history_valid: historyValid,
|
|
1085
|
+
has_creative_deadline: hasCreativeDeadline,
|
|
1086
|
+
sandbox: mediaBuy.sandbox,
|
|
1087
|
+
}, null, 2);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
else if (statusResult && !statusResult.success) {
|
|
1091
|
+
statusStep.passed = false;
|
|
1092
|
+
statusStep.error = statusResult.error || 'get_media_buys failed';
|
|
1093
|
+
}
|
|
1094
|
+
steps.push(statusStep);
|
|
1095
|
+
}
|
|
1096
|
+
// Step 3b: Revision concurrency check — use actual stale revision from an earlier step
|
|
1097
|
+
const lastRevision = revisions.length > 0 ? revisions[revisions.length - 1].revision : undefined;
|
|
1098
|
+
const staleRevision = revisions.length >= 2 ? revisions[0].revision : -1;
|
|
1099
|
+
const { result: conflictResult, step: conflictStep } = await (0, client_1.runStep)('Update with stale revision (expect CONFLICT)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1100
|
+
media_buy_id: mediaBuyId,
|
|
1101
|
+
revision: staleRevision,
|
|
1102
|
+
end_time: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
1103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1104
|
+
}));
|
|
1105
|
+
if (conflictResult && !conflictResult.success) {
|
|
1106
|
+
const error = conflictResult.error || '';
|
|
1107
|
+
if (error.includes('CONFLICT') || error.includes('conflict') || error.includes('revision')) {
|
|
1108
|
+
conflictStep.passed = true;
|
|
1109
|
+
conflictStep.details = `Correctly rejected stale revision ${staleRevision} with CONFLICT (current: ${lastRevision ?? 'unknown'})`;
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
// Agent rejected for another reason — still acceptable, revision may not be supported
|
|
1113
|
+
conflictStep.passed = true;
|
|
1114
|
+
conflictStep.details = `Agent rejected update: ${error}`;
|
|
1115
|
+
conflictStep.warnings = [
|
|
1116
|
+
'Agent did not return CONFLICT for stale revision — revision concurrency may not be supported',
|
|
1117
|
+
];
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
else if (conflictResult?.success) {
|
|
1121
|
+
// Agent accepted stale revision — revision concurrency not enforced
|
|
1122
|
+
conflictStep.passed = true;
|
|
1123
|
+
conflictStep.details = `Agent accepted stale revision ${staleRevision} — optimistic concurrency not enforced`;
|
|
1124
|
+
conflictStep.warnings = ['Agent does not enforce optimistic concurrency via revision numbers'];
|
|
1125
|
+
}
|
|
1126
|
+
else if (!conflictResult) {
|
|
1127
|
+
conflictStep.passed = false;
|
|
1128
|
+
conflictStep.error = 'No response from update_media_buy';
|
|
1129
|
+
}
|
|
1130
|
+
steps.push(conflictStep);
|
|
1131
|
+
// Step 3c: Check revision monotonicity
|
|
1132
|
+
if (revisions.length >= 2) {
|
|
1133
|
+
const monotonicStep = {
|
|
1134
|
+
step: 'Revision monotonicity check',
|
|
1135
|
+
task: 'update_media_buy',
|
|
1136
|
+
passed: true,
|
|
1137
|
+
duration_ms: 0,
|
|
1138
|
+
};
|
|
1139
|
+
const isMonotonic = revisions.every((r, i) => i === 0 || r.revision > revisions[i - 1].revision);
|
|
1140
|
+
if (isMonotonic) {
|
|
1141
|
+
monotonicStep.details = `Revisions are monotonically increasing: ${revisions.map(r => `${r.step}=${r.revision}`).join(' → ')}`;
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
monotonicStep.passed = true; // advisory, not a hard fail
|
|
1145
|
+
monotonicStep.details = `Revisions: ${revisions.map(r => `${r.step}=${r.revision}`).join(' → ')}`;
|
|
1146
|
+
monotonicStep.warnings = [
|
|
1147
|
+
`Revision numbers are not monotonically increasing — buyers depend on this for concurrency safety`,
|
|
1148
|
+
];
|
|
1149
|
+
}
|
|
1150
|
+
steps.push(monotonicStep);
|
|
1151
|
+
}
|
|
1152
|
+
// Step 4: Cancel the media buy
|
|
1153
|
+
const { result: cancelResult, step: cancelStep } = await (0, client_1.runStep)('Cancel media buy', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1154
|
+
media_buy_id: mediaBuyId,
|
|
1155
|
+
canceled: true,
|
|
1156
|
+
cancellation_reason: 'AdCP compliance test — lifecycle scenario',
|
|
1157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1158
|
+
}));
|
|
1159
|
+
if (cancelResult?.success && cancelResult?.data) {
|
|
1160
|
+
const data = cancelResult.data;
|
|
1161
|
+
const status = extractStatus(data);
|
|
1162
|
+
const cancelRevision = data.revision;
|
|
1163
|
+
cancelStep.details = `Canceled media buy, status: ${status}`;
|
|
1164
|
+
const canceledBy = data.canceled_by;
|
|
1165
|
+
const canceledAt = data.canceled_at;
|
|
1166
|
+
if (cancelRevision !== undefined)
|
|
1167
|
+
revisions.push({ step: 'cancel', revision: cancelRevision });
|
|
1168
|
+
cancelStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, status, revision: cancelRevision, canceled_by: canceledBy, canceled_at: canceledAt }, null, 2);
|
|
1169
|
+
if (status && status !== 'canceled') {
|
|
1170
|
+
cancelStep.warnings = [`Expected status 'canceled', got '${status}'`];
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
else if (cancelResult && !cancelResult.success) {
|
|
1174
|
+
// NOT_CANCELLABLE is a valid response — agent may not support cancellation
|
|
1175
|
+
const error = cancelResult.error || '';
|
|
1176
|
+
if (error.includes('NOT_CANCELLABLE') || error.includes('not_cancellable')) {
|
|
1177
|
+
cancelStep.passed = true;
|
|
1178
|
+
cancelStep.details = 'Agent does not support cancellation (NOT_CANCELLABLE)';
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
cancelStep.passed = false;
|
|
1182
|
+
cancelStep.error = cancelResult.error || 'Cancel operation failed';
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
steps.push(cancelStep);
|
|
1186
|
+
return { steps, profile };
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Test: Terminal State Enforcement
|
|
1190
|
+
* Verifies agents reject updates to media buys in terminal states.
|
|
1191
|
+
*/
|
|
1192
|
+
async function testTerminalStateEnforcement(agentUrl, options) {
|
|
1193
|
+
const steps = [];
|
|
1194
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
1195
|
+
// Create and cancel a media buy
|
|
1196
|
+
const { steps: createSteps, profile, mediaBuyId } = await testCreateMediaBuy(agentUrl, options);
|
|
1197
|
+
steps.push(...createSteps);
|
|
1198
|
+
if (!mediaBuyId || !profile?.tools.includes('update_media_buy')) {
|
|
1199
|
+
return { steps, profile };
|
|
1200
|
+
}
|
|
1201
|
+
// Cancel the media buy to put it in a terminal state
|
|
1202
|
+
const { result: cancelResult, step: cancelStep } = await (0, client_1.runStep)('Cancel media buy (setup)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1203
|
+
media_buy_id: mediaBuyId,
|
|
1204
|
+
canceled: true,
|
|
1205
|
+
cancellation_reason: 'AdCP compliance test — terminal state enforcement',
|
|
1206
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1207
|
+
}));
|
|
1208
|
+
if (cancelResult?.success && cancelResult?.data) {
|
|
1209
|
+
const data = cancelResult.data;
|
|
1210
|
+
cancelStep.details = `Canceled media buy, status: ${extractStatus(data)}`;
|
|
1211
|
+
}
|
|
1212
|
+
else if (cancelResult && !cancelResult.success) {
|
|
1213
|
+
const error = cancelResult.error || '';
|
|
1214
|
+
if (error.includes('NOT_CANCELLABLE') || error.includes('not_cancellable')) {
|
|
1215
|
+
// Agent doesn't support cancellation — try to find a completed buy instead
|
|
1216
|
+
cancelStep.passed = true;
|
|
1217
|
+
cancelStep.details = 'Agent does not support cancellation — will check for completed media buys instead';
|
|
1218
|
+
steps.push(cancelStep);
|
|
1219
|
+
// Look for a completed media buy to test terminal state enforcement against
|
|
1220
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1221
|
+
const { result: completedResult, step: completedStep } = await (0, client_1.runStep)('Find completed media buy for terminal state test', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
1222
|
+
status_filter: ['completed'],
|
|
1223
|
+
pagination: { max_results: 1 },
|
|
1224
|
+
}));
|
|
1225
|
+
if (completedResult?.success && completedResult?.data) {
|
|
1226
|
+
const completedBuys = (completedResult.data.media_buys || []);
|
|
1227
|
+
if (completedBuys.length > 0) {
|
|
1228
|
+
const completedId = completedBuys[0].media_buy_id;
|
|
1229
|
+
completedStep.details = `Found completed media buy: ${completedId}`;
|
|
1230
|
+
steps.push(completedStep);
|
|
1231
|
+
// Try to update the completed media buy
|
|
1232
|
+
const { result: updateCompletedResult, step: updateCompletedStep } = await (0, client_1.runStep)('Update completed media buy (expect rejection)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1233
|
+
media_buy_id: completedId,
|
|
1234
|
+
paused: true,
|
|
1235
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1236
|
+
}));
|
|
1237
|
+
if (updateCompletedResult?.success) {
|
|
1238
|
+
updateCompletedStep.passed = false;
|
|
1239
|
+
updateCompletedStep.error =
|
|
1240
|
+
'Agent accepted update to completed media buy — should reject with INVALID_STATE';
|
|
1241
|
+
}
|
|
1242
|
+
else if (updateCompletedResult) {
|
|
1243
|
+
updateCompletedStep.passed = true;
|
|
1244
|
+
const err = updateCompletedResult.error || '';
|
|
1245
|
+
const hasCode = err.includes('INVALID_STATE') || err.includes('invalid_state');
|
|
1246
|
+
updateCompletedStep.details = hasCode
|
|
1247
|
+
? 'Correctly rejected update to completed media buy with INVALID_STATE'
|
|
1248
|
+
: `Correctly rejected update to completed media buy: ${err}`;
|
|
1249
|
+
if (!hasCode && err) {
|
|
1250
|
+
updateCompletedStep.warnings = ['Agent rejected the update but did not use INVALID_STATE error code'];
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
steps.push(updateCompletedStep);
|
|
1254
|
+
}
|
|
1255
|
+
else {
|
|
1256
|
+
completedStep.details = 'No completed media buys found — cannot test terminal state enforcement';
|
|
1257
|
+
steps.push(completedStep);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
else {
|
|
1261
|
+
completedStep.passed = false;
|
|
1262
|
+
completedStep.error = completedResult?.error || 'Failed to query for completed media buys';
|
|
1263
|
+
steps.push(completedStep);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return { steps, profile };
|
|
1267
|
+
}
|
|
1268
|
+
cancelStep.passed = false;
|
|
1269
|
+
cancelStep.error = cancelResult.error || 'Cancel setup failed';
|
|
1270
|
+
}
|
|
1271
|
+
steps.push(cancelStep);
|
|
1272
|
+
// Try to pause the canceled media buy — should be rejected
|
|
1273
|
+
const { result: pauseTerminalResult, step: pauseTerminalStep } = await (0, client_1.runStep)('Update canceled media buy (expect rejection)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1274
|
+
media_buy_id: mediaBuyId,
|
|
1275
|
+
paused: true,
|
|
1276
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1277
|
+
}));
|
|
1278
|
+
if (pauseTerminalResult?.success) {
|
|
1279
|
+
pauseTerminalStep.passed = false;
|
|
1280
|
+
pauseTerminalStep.error = 'Agent accepted update to canceled media buy — should reject with INVALID_STATE';
|
|
1281
|
+
}
|
|
1282
|
+
else if (pauseTerminalResult) {
|
|
1283
|
+
// Agent returned { success: false } — correct behavior
|
|
1284
|
+
pauseTerminalStep.passed = true;
|
|
1285
|
+
const error = pauseTerminalResult.error || '';
|
|
1286
|
+
const hasExpectedCode = error.includes('INVALID_STATE') || error.includes('invalid_state');
|
|
1287
|
+
pauseTerminalStep.details = hasExpectedCode
|
|
1288
|
+
? 'Correctly rejected with INVALID_STATE'
|
|
1289
|
+
: `Correctly rejected update to canceled media buy: ${error}`;
|
|
1290
|
+
if (!hasExpectedCode && error) {
|
|
1291
|
+
pauseTerminalStep.warnings = ['Agent rejected the update but did not use INVALID_STATE error code'];
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
// else: result is undefined (exception thrown) — runStep already set passed=false and error
|
|
1295
|
+
steps.push(pauseTerminalStep);
|
|
1296
|
+
// Try to cancel again — should also be rejected (or idempotent)
|
|
1297
|
+
const { result: reCancelResult, step: reCancelStep } = await (0, client_1.runStep)('Cancel already-canceled media buy (expect rejection)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1298
|
+
media_buy_id: mediaBuyId,
|
|
1299
|
+
canceled: true,
|
|
1300
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1301
|
+
}));
|
|
1302
|
+
if (reCancelResult?.success) {
|
|
1303
|
+
// Idempotent cancellation is acceptable
|
|
1304
|
+
reCancelStep.passed = true;
|
|
1305
|
+
reCancelStep.details = 'Agent accepted re-cancellation (idempotent) — acceptable behavior';
|
|
1306
|
+
}
|
|
1307
|
+
else if (reCancelResult) {
|
|
1308
|
+
reCancelStep.passed = true;
|
|
1309
|
+
const error = reCancelResult.error || '';
|
|
1310
|
+
reCancelStep.details = `Correctly rejected re-cancellation: ${error}`;
|
|
1311
|
+
}
|
|
1312
|
+
steps.push(reCancelStep);
|
|
1313
|
+
// Also check completed terminal state if get_media_buys is available
|
|
1314
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1315
|
+
const { result: completedResult } = await (0, client_1.runStep)('Find completed media buy', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
1316
|
+
status_filter: ['completed'],
|
|
1317
|
+
pagination: { max_results: 1 },
|
|
1318
|
+
}));
|
|
1319
|
+
if (completedResult?.success && completedResult?.data) {
|
|
1320
|
+
const completedBuys = (completedResult.data.media_buys || []);
|
|
1321
|
+
if (completedBuys.length > 0) {
|
|
1322
|
+
const completedId = completedBuys[0].media_buy_id;
|
|
1323
|
+
const { result: updateCompletedResult, step: updateCompletedStep } = await (0, client_1.runStep)('Update completed media buy (expect rejection)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1324
|
+
media_buy_id: completedId,
|
|
1325
|
+
paused: true,
|
|
1326
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1327
|
+
}));
|
|
1328
|
+
if (updateCompletedResult?.success) {
|
|
1329
|
+
updateCompletedStep.passed = false;
|
|
1330
|
+
updateCompletedStep.error = 'Agent accepted update to completed media buy — should reject with INVALID_STATE';
|
|
1331
|
+
}
|
|
1332
|
+
else if (updateCompletedResult) {
|
|
1333
|
+
updateCompletedStep.passed = true;
|
|
1334
|
+
const err = updateCompletedResult.error || '';
|
|
1335
|
+
const hasCode = err.includes('INVALID_STATE') || err.includes('invalid_state');
|
|
1336
|
+
updateCompletedStep.details = hasCode
|
|
1337
|
+
? 'Correctly rejected update to completed media buy with INVALID_STATE'
|
|
1338
|
+
: `Correctly rejected update to completed media buy: ${err}`;
|
|
1339
|
+
}
|
|
1340
|
+
steps.push(updateCompletedStep);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
return { steps, profile };
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Test: Package Lifecycle
|
|
1348
|
+
* Tests package-level pause/resume independent of media buy status.
|
|
1349
|
+
*/
|
|
1350
|
+
async function testPackageLifecycle(agentUrl, options) {
|
|
1351
|
+
const steps = [];
|
|
1352
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
1353
|
+
// Create a media buy
|
|
1354
|
+
const { steps: createSteps, profile, mediaBuyId } = await testCreateMediaBuy(agentUrl, options);
|
|
1355
|
+
steps.push(...createSteps);
|
|
1356
|
+
if (!mediaBuyId || !profile?.tools.includes('update_media_buy')) {
|
|
1357
|
+
return { steps, profile };
|
|
1358
|
+
}
|
|
1359
|
+
// Find a package ID — try get_media_buys first, fall back to convention
|
|
1360
|
+
let packageId = 'pkg-0';
|
|
1361
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1362
|
+
const { result: fetchResult, step: fetchStep } = await (0, client_1.runStep)('Fetch package IDs', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
1363
|
+
media_buy_ids: [mediaBuyId],
|
|
1364
|
+
}));
|
|
1365
|
+
if (fetchResult?.success && fetchResult?.data) {
|
|
1366
|
+
const mediaBuys = (fetchResult.data.media_buys || []);
|
|
1367
|
+
const mediaBuy = mediaBuys.find((item) => item.media_buy_id === mediaBuyId) || mediaBuys[0];
|
|
1368
|
+
const packages = (mediaBuy?.packages || []);
|
|
1369
|
+
if (packages[0]?.package_id) {
|
|
1370
|
+
packageId = packages[0].package_id;
|
|
1371
|
+
fetchStep.details = `Found package ${packageId}`;
|
|
1372
|
+
}
|
|
1373
|
+
else {
|
|
1374
|
+
fetchStep.details = `No packages found, falling back to '${packageId}'`;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
else if (fetchResult && !fetchResult.success) {
|
|
1378
|
+
fetchStep.passed = false;
|
|
1379
|
+
fetchStep.error = fetchResult.error || 'get_media_buys failed during package ID discovery';
|
|
1380
|
+
}
|
|
1381
|
+
steps.push(fetchStep);
|
|
1382
|
+
}
|
|
1383
|
+
// Step 1: Pause a package
|
|
1384
|
+
const { result: pauseResult, step: pauseStep } = await (0, client_1.runStep)('Pause package', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1385
|
+
media_buy_id: mediaBuyId,
|
|
1386
|
+
packages: [{ package_id: packageId, paused: true }],
|
|
1387
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1388
|
+
}));
|
|
1389
|
+
if (pauseResult?.success && pauseResult?.data) {
|
|
1390
|
+
const data = pauseResult.data;
|
|
1391
|
+
const affectedPackages = (data.affected_packages || []);
|
|
1392
|
+
const pkg = affectedPackages.find(p => p.package_id === packageId) || affectedPackages[0];
|
|
1393
|
+
pauseStep.details = `Paused package ${packageId}, paused: ${pkg?.paused}`;
|
|
1394
|
+
pauseStep.response_preview = JSON.stringify({
|
|
1395
|
+
media_buy_id: mediaBuyId,
|
|
1396
|
+
media_buy_status: extractStatus(data),
|
|
1397
|
+
package_id: pkg?.package_id,
|
|
1398
|
+
package_paused: pkg?.paused,
|
|
1399
|
+
}, null, 2);
|
|
1400
|
+
}
|
|
1401
|
+
else if (pauseResult && !pauseResult.success) {
|
|
1402
|
+
pauseStep.passed = false;
|
|
1403
|
+
pauseStep.error = pauseResult.error || 'Package pause failed';
|
|
1404
|
+
}
|
|
1405
|
+
steps.push(pauseStep);
|
|
1406
|
+
// Step 2: Resume the package
|
|
1407
|
+
const { result: resumeResult, step: resumeStep } = await (0, client_1.runStep)('Resume package', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1408
|
+
media_buy_id: mediaBuyId,
|
|
1409
|
+
packages: [{ package_id: packageId, paused: false }],
|
|
1410
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1411
|
+
}));
|
|
1412
|
+
if (resumeResult?.success && resumeResult?.data) {
|
|
1413
|
+
const data = resumeResult.data;
|
|
1414
|
+
const affectedPackages = (data.affected_packages || []);
|
|
1415
|
+
const pkg = affectedPackages.find(p => p.package_id === packageId) || affectedPackages[0];
|
|
1416
|
+
resumeStep.details = `Resumed package ${packageId}, paused: ${pkg?.paused}`;
|
|
1417
|
+
resumeStep.response_preview = JSON.stringify({
|
|
1418
|
+
media_buy_id: mediaBuyId,
|
|
1419
|
+
media_buy_status: extractStatus(data),
|
|
1420
|
+
package_id: pkg?.package_id,
|
|
1421
|
+
package_paused: pkg?.paused,
|
|
1422
|
+
}, null, 2);
|
|
1423
|
+
}
|
|
1424
|
+
else if (resumeResult && !resumeResult.success) {
|
|
1425
|
+
resumeStep.passed = false;
|
|
1426
|
+
resumeStep.error = resumeResult.error || 'Package resume failed';
|
|
1427
|
+
}
|
|
1428
|
+
steps.push(resumeStep);
|
|
1429
|
+
// Step 3: Verify media buy is still active
|
|
1430
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1431
|
+
const { result: verifyResult, step: verifyStep } = await (0, client_1.runStep)('Verify media buy still active after package operations', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
1432
|
+
media_buy_ids: [mediaBuyId],
|
|
1433
|
+
}));
|
|
1434
|
+
if (verifyResult?.success && verifyResult?.data) {
|
|
1435
|
+
const mediaBuys = (verifyResult.data.media_buys || []);
|
|
1436
|
+
const mediaBuy = mediaBuys.find((item) => item.media_buy_id === mediaBuyId) || mediaBuys[0];
|
|
1437
|
+
const status = mediaBuy?.status;
|
|
1438
|
+
if (status === 'active' || status === 'pending_activation') {
|
|
1439
|
+
verifyStep.details = `Media buy still ${status} after package-level operations`;
|
|
1440
|
+
}
|
|
1441
|
+
else {
|
|
1442
|
+
verifyStep.details = `Media buy status is '${status}' — expected 'active' or 'pending_activation'`;
|
|
1443
|
+
verifyStep.warnings = [`Package-level pause/resume changed media buy status to '${status}'`];
|
|
1444
|
+
}
|
|
1445
|
+
verifyStep.response_preview = JSON.stringify({ media_buy_id: mediaBuy?.media_buy_id, status }, null, 2);
|
|
1446
|
+
}
|
|
1447
|
+
else if (verifyResult && !verifyResult.success) {
|
|
1448
|
+
verifyStep.passed = false;
|
|
1449
|
+
verifyStep.error = verifyResult.error || 'get_media_buys verification failed';
|
|
1450
|
+
}
|
|
1451
|
+
steps.push(verifyStep);
|
|
1452
|
+
}
|
|
1453
|
+
return { steps, profile };
|
|
1454
|
+
}
|
|
908
1455
|
//# sourceMappingURL=media-buy.js.map
|