openclaw/extensions/plivo/test-integration.ts
Narayan Vyas 40538b5a30 feat(channels): add Plivo SMS/MMS channel extension
Add a new channel extension for Plivo SMS/MMS messaging, enabling
universal phone-based access to Clawdbot from any device.

Features:
- Two-way SMS messaging via Plivo API
- MMS media support (images, videos, documents)
- Quick command shortcuts (e.g., "cal" -> "show my calendar")
- Auto-configuration of Plivo webhooks on startup
- Multi-account support
- DM policies (pairing, allowlist, open)

Implements ideas 3.1, 3.3, 3.4, 3.7 from PLIVO_INTEGRATION_IDEAS.md:
- 3.1 SMS Channel Adapter
- 3.3 Quick Commands via SMS
- 3.4 MMS Media Sharing
- 3.7 Critical Alerts via SMS

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 20:36:22 -08:00

210 lines
6.2 KiB
TypeScript

/**
* Integration test for Plivo SMS extension
* Tests core functionality without requiring real Plivo credentials
*/
import { createServer } from "node:http";
import { plivoPlugin } from "./src/channel.js";
import { startWebhookServer } from "./src/webhook.js";
import type { PlivoResolvedAccount } from "./src/types.js";
const TEST_PORT = 19876;
// Mock account config
const mockAccount: PlivoResolvedAccount = {
authId: "test_auth_id",
authToken: "test_auth_token",
phoneNumber: "+15551234567",
webhookPath: "/plivo",
dmPolicy: "open",
allowFrom: [],
enableQuickCommands: true,
quickCommands: [
{ trigger: "cal", fullCommand: "show my calendar for today", description: "Calendar" },
{ trigger: "todo", fullCommand: "show my todo list", description: "Todos" },
],
};
// Mock Clawdbot config
const mockConfig = {
channels: {
plivo: {
authId: "test_auth_id",
authToken: "test_auth_token",
phoneNumber: "+15551234567",
enabled: true,
},
},
};
async function testConfigAdapter() {
console.log("\n📋 Testing Config Adapter...");
// Test listAccountIds
const accountIds = plivoPlugin.config.listAccountIds(mockConfig);
console.log(` ✓ listAccountIds: ${JSON.stringify(accountIds)}`);
if (accountIds.length !== 1 || accountIds[0] !== "default") {
throw new Error("listAccountIds failed");
}
// Test isConfigured
const isConfigured = plivoPlugin.config.isConfigured(mockConfig);
console.log(` ✓ isConfigured: ${isConfigured}`);
if (!isConfigured) {
throw new Error("isConfigured should be true");
}
// Test describeAccount
const description = plivoPlugin.config.describeAccount(mockConfig);
console.log(` ✓ describeAccount: ${JSON.stringify(description)}`);
console.log(" ✅ Config adapter tests passed!");
}
async function testOutboundAdapter() {
console.log("\n📤 Testing Outbound Adapter...");
// Test resolveTarget
const validTarget = plivoPlugin.outbound.resolveTarget("+15559876543");
console.log(` ✓ resolveTarget (valid): ${JSON.stringify(validTarget)}`);
if (!validTarget.ok) {
throw new Error("resolveTarget should succeed for valid number");
}
const invalidTarget = plivoPlugin.outbound.resolveTarget("abc");
console.log(` ✓ resolveTarget (invalid): ${JSON.stringify(invalidTarget)}`);
if (invalidTarget.ok) {
throw new Error("resolveTarget should fail for invalid number");
}
// Test 10-digit US number normalization
const usNumber = plivoPlugin.outbound.resolveTarget("5559876543");
console.log(` ✓ resolveTarget (10-digit): ${JSON.stringify(usNumber)}`);
if (usNumber.to !== "+15559876543") {
throw new Error("10-digit number should be normalized to +1");
}
console.log(" ✅ Outbound adapter tests passed!");
}
async function testWebhookServer() {
console.log("\n🌐 Testing Webhook Server...");
let receivedMessage: unknown = null;
const { server, stop } = await startWebhookServer({
account: mockAccount,
accountId: "test",
path: "/plivo",
port: TEST_PORT,
onMessage: async (message) => {
receivedMessage = message;
return "Test reply";
},
log: () => {}, // Silent logging for tests
});
console.log(` ✓ Webhook server started on port ${TEST_PORT}`);
// Test health endpoint
const healthResponse = await fetch(`http://localhost:${TEST_PORT}/plivo/health`);
const healthData = await healthResponse.json();
console.log(` ✓ Health check: ${JSON.stringify(healthData)}`);
if (healthData.status !== "healthy") {
throw new Error("Health check failed");
}
// Test inbound SMS webhook
const inboundPayload = new URLSearchParams({
From: "+15559876543",
To: "+15551234567",
Text: "Hello world",
MessageUUID: "test-uuid-123",
Type: "sms",
});
const inboundResponse = await fetch(`http://localhost:${TEST_PORT}/plivo`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: inboundPayload.toString(),
});
console.log(` ✓ Inbound webhook status: ${inboundResponse.status}`);
if (inboundResponse.status !== 200) {
throw new Error("Inbound webhook failed");
}
// Verify message was received
if (!receivedMessage) {
throw new Error("Message handler was not called");
}
console.log(` ✓ Received message: ${JSON.stringify(receivedMessage)}`);
// Test quick command expansion
receivedMessage = null;
const quickCommandPayload = new URLSearchParams({
From: "+15559876543",
To: "+15551234567",
Text: "cal",
MessageUUID: "test-uuid-456",
Type: "sms",
});
await fetch(`http://localhost:${TEST_PORT}/plivo`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: quickCommandPayload.toString(),
});
const msg = receivedMessage as { text: string };
console.log(` ✓ Quick command expanded: "${msg?.text}"`);
if (msg?.text !== "show my calendar for today") {
throw new Error("Quick command not expanded correctly");
}
// Stop server
await stop();
console.log(" ✓ Webhook server stopped");
console.log(" ✅ Webhook server tests passed!");
}
async function testCapabilities() {
console.log("\n🔧 Testing Channel Capabilities...");
console.log(` ✓ Channel ID: ${plivoPlugin.id}`);
console.log(` ✓ Supports text: ${plivoPlugin.capabilities.supportsText}`);
console.log(` ✓ Supports media: ${plivoPlugin.capabilities.supportsMedia}`);
console.log(` ✓ Max text length: ${plivoPlugin.capabilities.maxTextLength}`);
if (!plivoPlugin.capabilities.supportsText) {
throw new Error("Should support text");
}
if (!plivoPlugin.capabilities.supportsMedia) {
throw new Error("Should support media");
}
console.log(" ✅ Capabilities tests passed!");
}
async function runAllTests() {
console.log("🧪 Plivo SMS Extension Integration Tests\n");
console.log("=========================================");
try {
await testCapabilities();
await testConfigAdapter();
await testOutboundAdapter();
await testWebhookServer();
console.log("\n=========================================");
console.log("✅ All integration tests passed!\n");
process.exit(0);
} catch (error) {
console.error("\n❌ Test failed:", error);
process.exit(1);
}
}
runAllTests();