mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
Merge branch 'feat/e2e-test' of github.com:selfxyz/self into feat/e2e-test
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Unified API test script for comparing TypeScript and Go API responses
|
||||
// Simplified API comparison test for TypeScript vs Go APIs
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
@@ -8,474 +8,143 @@ import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Configuration
|
||||
const TS_API_URL = "http://localhost:3000";
|
||||
const GO_API_URL = "http://localhost:8080";
|
||||
const VERIFY_ENDPOINT = "/api/verify";
|
||||
|
||||
// Colors for console output
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
white: '\x1b[37m'
|
||||
};
|
||||
|
||||
function colorize(text, color) {
|
||||
return `${colors[color]}${text}${colors.reset}`;
|
||||
}
|
||||
|
||||
// Test case structure
|
||||
class TestCase {
|
||||
constructor(name, requestBody, expectedStatus = 200, expectedResult = true, expectedErrorMessage = null) {
|
||||
this.name = name;
|
||||
this.requestBody = requestBody;
|
||||
this.expectedStatus = expectedStatus;
|
||||
this.expectedResult = expectedResult;
|
||||
this.expectedErrorMessage = expectedErrorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// API response comparison utility
|
||||
class APIComparison {
|
||||
constructor() {
|
||||
this.results = {
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
tests: []
|
||||
};
|
||||
}
|
||||
|
||||
async makeAPICall(url, endpoint, requestBody) {
|
||||
const fullUrl = `${url}${endpoint}`;
|
||||
// Simple API call function
|
||||
async function callAPI(url, requestBody) {
|
||||
try {
|
||||
const response = await fetch(`${url}${VERIFY_ENDPOINT}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
const data = await response.text();
|
||||
let parsedData;
|
||||
try {
|
||||
const response = await fetch(fullUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
let responseData;
|
||||
|
||||
try {
|
||||
responseData = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
responseData = { rawResponse: responseText };
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
data: responseData,
|
||||
success: true
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 0,
|
||||
data: { error: error.message },
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async runTest(testCase) {
|
||||
console.log(colorize(`\n🧪 Running test: ${testCase.name}`, 'cyan'));
|
||||
console.log(colorize('=' + '='.repeat(50 + testCase.name.length), 'cyan'));
|
||||
|
||||
// Make calls to both APIs
|
||||
console.log(colorize('\n📡 Making API calls...', 'blue'));
|
||||
|
||||
const [tsResponse, goResponse] = await Promise.all([
|
||||
this.makeAPICall(TS_API_URL, VERIFY_ENDPOINT, testCase.requestBody),
|
||||
this.makeAPICall(GO_API_URL, VERIFY_ENDPOINT, testCase.requestBody)
|
||||
]);
|
||||
|
||||
// Display responses
|
||||
console.log(colorize('\n📊 TypeScript API Response:', 'magenta'));
|
||||
console.log(`Status: ${tsResponse.status}`);
|
||||
console.log(`Success: ${tsResponse.success}`);
|
||||
if (tsResponse.error) {
|
||||
console.log(colorize(`Error: ${tsResponse.error}`, 'red'));
|
||||
} else {
|
||||
console.log(JSON.stringify(tsResponse.data, null, 2));
|
||||
}
|
||||
|
||||
console.log(colorize('\n📊 Go API Response:', 'magenta'));
|
||||
console.log(`Status: ${goResponse.status}`);
|
||||
console.log(`Success: ${goResponse.success}`);
|
||||
if (goResponse.error) {
|
||||
console.log(colorize(`Error: ${goResponse.error}`, 'red'));
|
||||
} else {
|
||||
console.log(JSON.stringify(goResponse.data, null, 2));
|
||||
}
|
||||
|
||||
// Compare responses
|
||||
const comparison = this.compareResponses(testCase, tsResponse, goResponse);
|
||||
|
||||
// Record results
|
||||
if (comparison.passed) {
|
||||
this.results.passed++;
|
||||
console.log(colorize('\n✅ Test PASSED', 'green'));
|
||||
} else {
|
||||
this.results.failed++;
|
||||
console.log(colorize('\n❌ Test FAILED', 'red'));
|
||||
}
|
||||
|
||||
// Display comparison details
|
||||
console.log(colorize('\n🔍 Comparison Results:', 'yellow'));
|
||||
comparison.details.forEach(detail => {
|
||||
const color = detail.passed ? 'green' : 'red';
|
||||
const symbol = detail.passed ? '✓' : '✗';
|
||||
console.log(colorize(` ${symbol} ${detail.message}`, color));
|
||||
});
|
||||
|
||||
this.results.tests.push({
|
||||
name: testCase.name,
|
||||
passed: comparison.passed,
|
||||
details: comparison.details,
|
||||
tsResponse,
|
||||
goResponse
|
||||
});
|
||||
|
||||
return comparison;
|
||||
}
|
||||
|
||||
compareResponses(testCase, tsResponse, goResponse) {
|
||||
const details = [];
|
||||
let passed = true;
|
||||
|
||||
// Check if both APIs are reachable
|
||||
if (!tsResponse.success && !goResponse.success) {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: 'Both APIs are unreachable - make sure Docker services are running'
|
||||
});
|
||||
return { passed: false, details };
|
||||
}
|
||||
|
||||
if (!tsResponse.success) {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: `TypeScript API unreachable: ${tsResponse.error}`
|
||||
});
|
||||
passed = false;
|
||||
}
|
||||
|
||||
if (!goResponse.success) {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: `Go API unreachable: ${goResponse.error}`
|
||||
});
|
||||
passed = false;
|
||||
}
|
||||
|
||||
if (!tsResponse.success || !goResponse.success) {
|
||||
return { passed, details };
|
||||
}
|
||||
|
||||
// Compare HTTP status codes
|
||||
if (tsResponse.status === goResponse.status) {
|
||||
details.push({
|
||||
passed: true,
|
||||
message: `Status codes match: ${tsResponse.status}`
|
||||
});
|
||||
} else {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: `Status codes differ - TS: ${tsResponse.status}, Go: ${goResponse.status}`
|
||||
});
|
||||
passed = false;
|
||||
}
|
||||
|
||||
// Check expected status
|
||||
if (tsResponse.status === testCase.expectedStatus && goResponse.status === testCase.expectedStatus) {
|
||||
details.push({
|
||||
passed: true,
|
||||
message: `Both APIs returned expected status: ${testCase.expectedStatus}`
|
||||
});
|
||||
} else {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: `Expected status ${testCase.expectedStatus}, got TS: ${tsResponse.status}, Go: ${goResponse.status}`
|
||||
});
|
||||
passed = false;
|
||||
}
|
||||
|
||||
// Compare result field (if present)
|
||||
const tsResult = tsResponse.data?.result;
|
||||
const goResult = goResponse.data?.result;
|
||||
|
||||
if (tsResult !== undefined && goResult !== undefined) {
|
||||
if (tsResult === goResult) {
|
||||
details.push({
|
||||
passed: true,
|
||||
message: `Result fields match: ${tsResult}`
|
||||
});
|
||||
} else {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: `Result fields differ - TS: ${tsResult}, Go: ${goResult}`
|
||||
});
|
||||
passed = false;
|
||||
}
|
||||
|
||||
// Check expected result
|
||||
if (tsResult === testCase.expectedResult && goResult === testCase.expectedResult) {
|
||||
details.push({
|
||||
passed: true,
|
||||
message: `Both APIs returned expected result: ${testCase.expectedResult}`
|
||||
});
|
||||
} else {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: `Expected result ${testCase.expectedResult}, got TS: ${tsResult}, Go: ${goResult}`
|
||||
});
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare error messages (if expected)
|
||||
if (testCase.expectedErrorMessage) {
|
||||
const tsMessage = tsResponse.data?.message || tsResponse.data?.error;
|
||||
const goMessage = goResponse.data?.message || goResponse.data?.error;
|
||||
|
||||
if (tsMessage && goMessage) {
|
||||
// Check if both contain the expected error message
|
||||
const tsContainsExpected = tsMessage.toLowerCase().includes(testCase.expectedErrorMessage.toLowerCase());
|
||||
const goContainsExpected = goMessage.toLowerCase().includes(testCase.expectedErrorMessage.toLowerCase());
|
||||
|
||||
if (tsContainsExpected && goContainsExpected) {
|
||||
details.push({
|
||||
passed: true,
|
||||
message: `Both APIs returned expected error message containing: "${testCase.expectedErrorMessage}"`
|
||||
});
|
||||
} else {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: `Error messages don't match expected. TS: "${tsMessage}", Go: "${goMessage}", Expected: "${testCase.expectedErrorMessage}"`
|
||||
});
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare response structure similarity
|
||||
const tsKeys = Object.keys(tsResponse.data || {}).sort();
|
||||
const goKeys = Object.keys(goResponse.data || {}).sort();
|
||||
|
||||
if (JSON.stringify(tsKeys) === JSON.stringify(goKeys)) {
|
||||
details.push({
|
||||
passed: true,
|
||||
message: `Response structures match (same fields)`
|
||||
});
|
||||
} else {
|
||||
details.push({
|
||||
passed: false,
|
||||
message: `Response structures differ - TS fields: [${tsKeys.join(', ')}], Go fields: [${goKeys.join(', ')}]`
|
||||
});
|
||||
// This is a warning, not a failure
|
||||
}
|
||||
|
||||
return { passed, details };
|
||||
}
|
||||
|
||||
printSummary() {
|
||||
console.log(colorize('\n' + '='.repeat(60), 'cyan'));
|
||||
console.log(colorize('📋 TEST SUMMARY', 'cyan'));
|
||||
console.log(colorize('='.repeat(60), 'cyan'));
|
||||
|
||||
const total = this.results.passed + this.results.failed;
|
||||
console.log(`\n📊 Total Tests: ${total}`);
|
||||
console.log(colorize(`✅ Passed: ${this.results.passed}`, 'green'));
|
||||
console.log(colorize(`❌ Failed: ${this.results.failed}`, 'red'));
|
||||
|
||||
if (this.results.failed === 0) {
|
||||
console.log(colorize('\n🎉 All tests passed! Both APIs are responding consistently.', 'green'));
|
||||
} else {
|
||||
console.log(colorize('\n⚠️ Some tests failed. Check the details above.', 'yellow'));
|
||||
}
|
||||
|
||||
// Show failed tests
|
||||
if (this.results.failed > 0) {
|
||||
console.log(colorize('\n❌ Failed Tests:', 'red'));
|
||||
this.results.tests.filter(t => !t.passed).forEach(test => {
|
||||
console.log(colorize(` • ${test.name}`, 'red'));
|
||||
});
|
||||
parsedData = JSON.parse(data);
|
||||
} catch {
|
||||
parsedData = { rawResponse: data };
|
||||
}
|
||||
return {
|
||||
status: response.status,
|
||||
data: parsedData,
|
||||
success: true
|
||||
};
|
||||
} catch (error) {
|
||||
return { status: 0, data: { error: error.message }, success: false };
|
||||
}
|
||||
}
|
||||
|
||||
// Load test data
|
||||
// Compare two API responses
|
||||
function compareAPIs(testName, tsResponse, goResponse, expectedStatus = 200) {
|
||||
const issues = [];
|
||||
|
||||
// Check connectivity
|
||||
if (!tsResponse.success) issues.push(`TS API unreachable: ${tsResponse.data.error}`);
|
||||
if (!goResponse.success) issues.push(`Go API unreachable: ${goResponse.data.error}`);
|
||||
if (issues.length) return { passed: false, issues };
|
||||
|
||||
// Compare status codes
|
||||
if (tsResponse.status !== goResponse.status) {
|
||||
issues.push(`Status mismatch: TS=${tsResponse.status}, Go=${goResponse.status}`);
|
||||
}
|
||||
if (tsResponse.status !== expectedStatus) {
|
||||
issues.push(`Expected status ${expectedStatus}, got TS=${tsResponse.status}, Go=${goResponse.status}`);
|
||||
}
|
||||
|
||||
// Compare results
|
||||
const tsResult = tsResponse.data?.result;
|
||||
const goResult = goResponse.data?.result;
|
||||
if (tsResult !== undefined && goResult !== undefined && tsResult !== goResult) {
|
||||
issues.push(`Result mismatch: TS=${tsResult}, Go=${goResult}`);
|
||||
}
|
||||
|
||||
return { passed: issues.length === 0, issues };
|
||||
}
|
||||
|
||||
// Run a single test
|
||||
async function runTest(testName, requestBody, expectedStatus = 200) {
|
||||
console.log(`\n🧪 ${testName}`);
|
||||
|
||||
const [tsResponse, goResponse] = await Promise.all([
|
||||
callAPI(TS_API_URL, requestBody),
|
||||
callAPI(GO_API_URL, requestBody)
|
||||
]);
|
||||
|
||||
const result = compareAPIs(testName, tsResponse, goResponse, expectedStatus);
|
||||
|
||||
if (result.passed) {
|
||||
console.log(`✅ PASS`);
|
||||
} else {
|
||||
console.log(`❌ FAIL:`);
|
||||
result.issues.forEach(issue => console.log(` - ${issue}`));
|
||||
}
|
||||
|
||||
return result.passed;
|
||||
}
|
||||
|
||||
// Load test data and create test cases
|
||||
function loadTestData() {
|
||||
try {
|
||||
const proofDataPath = path.join(__dirname, 'ts-api', 'vc_and_disclose_proof.json');
|
||||
const proofData = JSON.parse(fs.readFileSync(proofDataPath, 'utf8'));
|
||||
return proofData;
|
||||
return JSON.parse(fs.readFileSync(proofDataPath, 'utf8'));
|
||||
} catch (error) {
|
||||
console.error(colorize(`❌ Error loading test data: ${error.message}`, 'red'));
|
||||
console.error(`❌ Error loading test data: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Create test cases
|
||||
function createTestCases() {
|
||||
const proofData = loadTestData();
|
||||
const validUserContext = "000000000000000000000000000000000000000000000000000000000000a4ec00000000000000000000000094ba0db8a9db66979905784a9d6b2d286e55bd27";
|
||||
const invalidUserContext = "000000000000000000000000000000000000000000000000000000000000a4ec00000000000000000000000094ba0db8a9db66979905784a9d6b2d286e55bd28";
|
||||
|
||||
// Valid userContextData from working test
|
||||
const validUserContextData = "000000000000000000000000000000000000000000000000000000000000a4ec00000000000000000000000094ba0db8a9db66979905784a9d6b2d286e55bd27";
|
||||
|
||||
// Create invalid userContextData by modifying the userIdentifier part (bytes 64-128)
|
||||
// Original: "00000000000000000000000094ba0db8a9db66979905784a9d6b2d286e55bd27"
|
||||
// Modified: "00000000000000000000000094ba0db8a9db66979905784a9d6b2d286e55bd28" (changed last byte)
|
||||
const invalidUserContextData = "000000000000000000000000000000000000000000000000000000000000a4ec00000000000000000000000094ba0db8a9db66979905784a9d6b2d286e55bd28";
|
||||
|
||||
// Create modified publicSignals for different error scenarios
|
||||
const invalidScopeSignals = [...proofData.publicSignals];
|
||||
invalidScopeSignals[19] = "17121382998761176299335602807450250650083579600718579431641003529012841023067"; // Changed scope (index 19)
|
||||
|
||||
const invalidMerkleRootSignals = [...proofData.publicSignals];
|
||||
invalidMerkleRootSignals[9] = "9656656992379025128519272376477139373854042233370909906627112932049610896732"; // Changed merkle root (index 9)
|
||||
|
||||
const invalidAttestationIdSignals = [...proofData.publicSignals];
|
||||
invalidAttestationIdSignals[8] = "2"; // Changed attestation ID from "1" to "2" (index 8)
|
||||
|
||||
const testCases = [
|
||||
new TestCase(
|
||||
'Valid Proof Verification',
|
||||
{
|
||||
return [
|
||||
{
|
||||
name: 'Valid Proof Verification',
|
||||
body: { attestationId: 1, proof: proofData.proof, publicSignals: proofData.publicSignals, userContextData: validUserContext },
|
||||
expectedStatus: 200
|
||||
},
|
||||
{
|
||||
name: 'Invalid User Context',
|
||||
body: { attestationId: 1, proof: proofData.proof, publicSignals: proofData.publicSignals, userContextData: invalidUserContext },
|
||||
expectedStatus: 500
|
||||
},
|
||||
{
|
||||
name: 'Invalid Scope',
|
||||
body: {
|
||||
attestationId: 1,
|
||||
proof: proofData.proof,
|
||||
publicSignals: proofData.publicSignals,
|
||||
userContextData: validUserContextData
|
||||
publicSignals: proofData.publicSignals.map((sig, i) => i === 19 ? "17121382998761176299335602807450250650083579600718579431641003529012841023067" : sig),
|
||||
userContextData: validUserContext
|
||||
},
|
||||
200,
|
||||
true
|
||||
),
|
||||
new TestCase(
|
||||
'UserContextHash Mismatch Should Fail',
|
||||
{
|
||||
attestationId: 1,
|
||||
proof: proofData.proof,
|
||||
publicSignals: proofData.publicSignals,
|
||||
userContextData: invalidUserContextData
|
||||
},
|
||||
500,
|
||||
false,
|
||||
"User context hash does not match"
|
||||
),
|
||||
new TestCase(
|
||||
'Invalid Scope Should Fail',
|
||||
{
|
||||
attestationId: 1,
|
||||
proof: proofData.proof,
|
||||
publicSignals: invalidScopeSignals,
|
||||
userContextData: validUserContextData
|
||||
},
|
||||
500,
|
||||
false,
|
||||
"Scope does not match"
|
||||
),
|
||||
new TestCase(
|
||||
'Invalid Merkle Root Should Fail',
|
||||
{
|
||||
attestationId: 1,
|
||||
proof: proofData.proof,
|
||||
publicSignals: invalidMerkleRootSignals,
|
||||
userContextData: validUserContextData
|
||||
},
|
||||
500,
|
||||
false,
|
||||
"root does not exist"
|
||||
),
|
||||
new TestCase(
|
||||
'Invalid Attestation ID Should Fail',
|
||||
{
|
||||
attestationId: 1,
|
||||
proof: proofData.proof,
|
||||
publicSignals: invalidAttestationIdSignals,
|
||||
userContextData: validUserContextData
|
||||
},
|
||||
500,
|
||||
false,
|
||||
"Attestation ID does not match"
|
||||
)
|
||||
];
|
||||
|
||||
return testCases;
|
||||
}
|
||||
|
||||
// Check if APIs are running
|
||||
async function checkAPIHealth() {
|
||||
console.log(colorize('🔍 Checking API health...', 'blue'));
|
||||
|
||||
const healthChecks = await Promise.all([
|
||||
fetch(`${TS_API_URL}/health`).then(r => ({ api: 'TypeScript', status: r.status, ok: r.ok })).catch(e => ({ api: 'TypeScript', error: e.message, ok: false })),
|
||||
fetch(`${GO_API_URL}/health`).then(r => ({ api: 'Go', status: r.status, ok: r.ok })).catch(e => ({ api: 'Go', error: e.message, ok: false }))
|
||||
]);
|
||||
|
||||
let allHealthy = true;
|
||||
healthChecks.forEach(check => {
|
||||
if (check.ok) {
|
||||
console.log(colorize(`✅ ${check.api} API is healthy (${check.status})`, 'green'));
|
||||
} else {
|
||||
console.log(colorize(`❌ ${check.api} API is not responding: ${check.error || check.status}`, 'red'));
|
||||
allHealthy = false;
|
||||
expectedStatus: 500
|
||||
}
|
||||
});
|
||||
|
||||
if (!allHealthy) {
|
||||
console.log(colorize('\n⚠️ Some APIs are not responding. Make sure Docker services are running:', 'yellow'));
|
||||
console.log(' cd sdk/tests && ./run-apis.sh up');
|
||||
console.log('');
|
||||
console.log('Continuing with tests anyway...');
|
||||
}
|
||||
|
||||
return allHealthy;
|
||||
];
|
||||
}
|
||||
|
||||
// Main execution
|
||||
async function main() {
|
||||
console.log(colorize('🚀 Self SDK Unified API Test Suite', 'cyan'));
|
||||
console.log(colorize('=====================================', 'cyan'));
|
||||
console.log(' Self SDK API Comparison Test\n');
|
||||
|
||||
// Check API health
|
||||
await checkAPIHealth();
|
||||
|
||||
// Create test runner
|
||||
const comparison = new APIComparison();
|
||||
|
||||
// Create and run test cases
|
||||
const testCases = createTestCases();
|
||||
|
||||
console.log(colorize(`\n📋 Running ${testCases.length} test case(s)...`, 'blue'));
|
||||
let passed = 0, failed = 0;
|
||||
|
||||
for (const testCase of testCases) {
|
||||
await comparison.runTest(testCase);
|
||||
const success = await runTest(testCase.name, testCase.body, testCase.expectedStatus);
|
||||
success ? passed++ : failed++;
|
||||
}
|
||||
|
||||
// Print summary
|
||||
comparison.printSummary();
|
||||
console.log(`\n Results: ${passed} passed, ${failed} failed`);
|
||||
console.log(failed === 0 ? '🎉 All tests passed!' : ' Some tests failed');
|
||||
|
||||
// Exit with appropriate code
|
||||
process.exit(comparison.results.failed > 0 ? 1 : 0);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
process.on('unhandledRejection', (error) => {
|
||||
console.error(colorize('\n💥 Unhandled error:', 'red'), error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Run the tests
|
||||
main().catch(error => {
|
||||
console.error(colorize('\n💥 Fatal error:', 'red'), error);
|
||||
console.error(` error: ${error}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user