import assert from 'node:assert/strict';
import { CISimulator } from './src/ci-simulator.js';
import { runAgentScenario, verifyCallback, cleanupCallback } from './src/agent-scenario.js';
class MockRuntime {
#callbacks = new Map();
#messages = [];
constructor() {
this.callbackBaseUrl = 'http://localhost:8080';
}
async createCallback({ summary, source, condition, delivery_mode }) {
const waiting_intent_id = `wi-${Date.now()}`;
const callback_descriptor_id = `cd-${Date.now()}`;
const token = this.#generateToken();
const callback_url = `${this.callbackBaseUrl}/callback/${token}`;
this.#callbacks.set(waiting_intent_id, {
waiting_intent_id,
callback_descriptor_id,
callback_url,
summary,
source,
condition,
delivery_mode,
});
return {
waiting_intent_id,
callback_descriptor_id,
callback_url,
target_agent_id: 'agent-test',
delivery_mode,
};
}
async cancelWaiting(waiting_intent_id) {
const callback = this.#callbacks.get(waiting_intent_id);
if (!callback) {
throw new Error(`Waiting intent ${waiting_intent_id} not found`);
}
this.#callbacks.delete(waiting_intent_id);
return {
waiting_intent_id,
callback_descriptor_id: callback.callback_descriptor_id,
status: 'Cancelled',
};
}
#generateToken() {
return `token-${Math.random().toString(36).substring(2, 15)}`;
}
async receiveCallback(payload) {
this.#messages.push({
id: `msg-${Date.now()}`,
origin: {
type: 'Callback',
descriptor_id: payload.descriptor_id,
source: payload.source,
},
body: {
json: payload.json,
},
metadata: {
waiting_intent_id: payload.waiting_intent_id,
callback_descriptor_id: payload.descriptor_id,
source: payload.source,
resource: payload.resource,
metadata: payload.metadata,
},
});
}
getMessages() {
return this.#messages;
}
getLastMessage() {
return this.#messages[this.#messages.length - 1];
}
_getCallback(waitingIntentId) {
return this.#callbacks.get(waitingIntentId);
}
_getRemainingCallbacksCount() {
return this.#callbacks.size;
}
}
async function runEnqueueMessageTest(runtime, ciSystem) {
console.log('\n--- Test 1: enqueue_message mode ---\n');
console.log('Step 1: Agent creates callback and starts build');
const scenario = await runAgentScenario(runtime, ciSystem);
console.log('');
ciSystem.setTestDeliveryFn(async (token, body) => {
const callback = runtime._getCallback(scenario.waiting_intent_id);
await runtime.receiveCallback({
waiting_intent_id: scenario.waiting_intent_id,
descriptor_id: callback.callback_descriptor_id,
source: 'ci-simulator',
json: body.json,
metadata: body.metadata,
});
});
console.log('Step 2: CI system completes build and delivers webhook');
await ciSystem.completeBuild(scenario.buildId, 'success');
console.log('');
console.log('Step 3: Verify agent received correct message');
const message = runtime.getLastMessage();
assert(message, 'Agent should have received a message');
const verified = verifyCallback(message, scenario.buildId);
assert(verified, 'Callback verification should pass');
console.log('');
console.log('Step 4: Clean up callback capability');
const cancelResult = await cleanupCallback(runtime, scenario.waiting_intent_id);
assert.equal(cancelResult.status, 'Cancelled');
console.log('');
const remainingCallbacks = runtime._getRemainingCallbacksCount();
assert.equal(remainingCallbacks, 0, 'All callbacks should be cancelled');
}
async function runWakeOnlyTest(runtime, ciSystem) {
console.log('\n--- Test 2: wake_only mode ---\n');
console.log('Step 1: Agent creates wake_only callback');
const wakeCallback = await runtime.createCallback({
summary: 'Wake-only notification for build',
source: 'ci-simulator',
condition: 'build_status != "running"',
delivery_mode: 'wake_only',
});
console.log('[Agent] Wake-only callback created:', {
waiting_intent_id: wakeCallback.waiting_intent_id,
callback_url: wakeCallback.callback_url,
});
const buildId = ciSystem.startBuild('holon/holon', 'def456');
ciSystem.registerWebhook({
callbackUrl: wakeCallback.callback_url,
buildId,
expectedStatus: 'success',
});
console.log('[Agent] Build started with wake_only webhook');
ciSystem.setTestDeliveryFn(async (token, body) => {
const storedCallback = runtime._getCallback(wakeCallback.waiting_intent_id);
await runtime.receiveCallback({
waiting_intent_id: wakeCallback.waiting_intent_id,
descriptor_id: storedCallback.callback_descriptor_id,
source: 'ci-simulator',
json: null, metadata: {
delivered_at: new Date().toISOString(),
},
});
});
console.log('Step 2: CI system completes build');
await ciSystem.completeBuild(buildId, 'success');
console.log('[Agent] Wake-only callback received');
const message = runtime.getLastMessage();
assert(message, 'Agent should receive wakeup signal');
assert.equal(message.origin.type, 'Callback');
assert.equal(message.body.json, null, 'wake_only should deliver no payload');
console.log('[Agent] Wake-only signal verified');
await cleanupCallback(runtime, wakeCallback.waiting_intent_id);
console.log('');
}
async function runCancelThenCallbackTest(runtime, ciSystem) {
console.log('\n--- Test 3: callback after CancelWaiting ---\n');
console.log('Step 1: Agent creates callback');
const callback = await runtime.createCallback({
summary: 'Test callback for cancellation',
source: 'ci-simulator',
condition: 'build_status != "running"',
delivery_mode: 'enqueue_message',
});
console.log('[Agent] Callback created:', {
waiting_intent_id: callback.waiting_intent_id,
callback_url: callback.callback_url,
});
const buildId = ciSystem.startBuild('holon/holon', 'ghi789');
ciSystem.registerWebhook({
callbackUrl: callback.callback_url,
buildId,
expectedStatus: 'success',
});
console.log('[Agent] Build started');
console.log('Step 2: Agent cancels waiting intent');
const cancelResult = await runtime.cancelWaiting(callback.waiting_intent_id);
assert.equal(cancelResult.status, 'Cancelled');
console.log('[Agent] Waiting intent cancelled:', cancelResult.waiting_intent_id);
let callbackAttempted = false;
let callbackRejected = false;
ciSystem.setTestDeliveryFn(async (token, body) => {
callbackAttempted = true;
try {
const storedCallback = runtime._getCallback(callback.waiting_intent_id);
if (!storedCallback) {
callbackRejected = true;
console.log('[Agent] Callback rejected: waiting intent not found');
return;
}
await runtime.receiveCallback({
waiting_intent_id: callback.waiting_intent_id,
descriptor_id: storedCallback.callback_descriptor_id,
source: 'ci-simulator',
json: body.json,
metadata: body.metadata,
});
} catch (err) {
callbackRejected = true;
console.log('[Agent] Callback rejected with error:', err.message);
}
});
console.log('Step 3: CI system completes build (callback should be ignored)');
await ciSystem.completeBuild(buildId, 'success');
assert(callbackAttempted, 'CI system should have attempted callback delivery');
assert(callbackRejected, 'Callback delivery should be rejected after cancellation');
console.log('[Agent] Callback correctly rejected after cancellation');
const messageCount = runtime.getMessages().length;
assert.equal(messageCount, 0, 'No messages should be delivered after cancellation');
console.log('[Agent] Verified: no messages delivered after cancellation');
console.log('');
}
async function main() {
console.log('=== Callback Capability End-to-End Test ===');
const runtime1 = new MockRuntime();
const ciSystem1 = new CISimulator('http://localhost:8080');
await runEnqueueMessageTest(runtime1, ciSystem1);
const runtime2 = new MockRuntime();
const ciSystem2 = new CISimulator('http://localhost:8080');
await runWakeOnlyTest(runtime2, ciSystem2);
const runtime3 = new MockRuntime();
const ciSystem3 = new CISimulator('http://localhost:8080');
await runCancelThenCallbackTest(runtime3, ciSystem3);
console.log('=== All tests passed ===');
}
main().catch((err) => {
console.error('Test failed:', err);
process.exit(1);
});