feat(test): add project seeding utilities for localhost testing

- Add seed-test-projects.js utility script
  - Generates test project data matching API schema
  - Creates projects with handleIds, jwtIds, planSummary, previousClaim
  - Supports export, seed, and generate commands
  - Can seed projects to localhost API server

- Add test-api-server-with-seed.js
  - Standalone Express server for localhost testing
  - Auto-seeds test projects on startup
  - Implements /api/v2/report/plansLastUpdatedBetween endpoint
  - Includes debugging endpoints (/api/test/projects, /api/test/health)
  - Ready to use immediately without database setup

- Update localhost testing guide
  - Add seeding instructions and examples
  - Document test API server usage
  - Explain how to integrate with existing API servers

This enables testing prefetch functionality even when your localhost
API has no project data. The test server can be started immediately
and provides 5 seeded test projects ready for prefetch queries.
This commit is contained in:
Matthew Raymer
2025-10-29 12:13:59 +00:00
parent 1bf39fd1f7
commit f5dca34e84
3 changed files with 491 additions and 7 deletions

249
scripts/seed-test-projects.js Executable file
View File

@@ -0,0 +1,249 @@
#!/usr/bin/env node
/**
* Seed Test Projects for Localhost Testing
*
* Creates test project data for localhost API server testing.
* Can be run standalone or integrated into your local dev server.
*
* @author Matthew Raymer
* @version 1.0.0
*/
const http = require('http');
/**
* Generate a test JWT ID with timestamp prefix for sorting
*/
function generateJwtId(timestamp = Date.now()) {
const random = Math.random().toString(36).substring(2, 8);
const hash = Math.random().toString(36).substring(2, 10);
return `${Math.floor(timestamp / 1000)}_${random}_${hash}`;
}
/**
* Generate test project data
*/
function generateTestProject(handleId, index = 0) {
const now = Date.now();
const startTime = new Date(now + (index * 86400000)); // 1 day apart
const endTime = new Date(startTime.getTime() + (30 * 86400000)); // 30 days later
return {
jwtId: generateJwtId(now + (index * 1000)),
handleId: handleId,
name: `Test Project ${index + 1}`,
description: `This is test project ${index + 1} for localhost prefetch testing. Created for User Zero starred plans querying.`,
issuerDid: "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F",
agentDid: "did:test:agent_" + index,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
locLat: 40.7128 + (index * 0.1), // Vary location slightly
locLon: -74.0060 + (index * 0.1),
url: `https://test-project-${index + 1}.timesafari.test`,
version: "1.0.0"
};
}
/**
* Generate complete test project with previous claim
*/
function generateTestProjectWithClaim(handleId, index = 0) {
const project = generateTestProject(handleId, index);
const previousClaimJwtId = generateJwtId(Date.now() - (86400000 * 2)); // 2 days ago
return {
planSummary: project,
previousClaim: {
jwtId: previousClaimJwtId,
claimType: "project_update",
claimData: {
message: `Previous update for ${handleId}`,
version: "0.9.0"
},
metadata: {
createdAt: new Date(Date.now() - (86400000 * 2)).toISOString(),
updatedAt: new Date(Date.now() - (86400000)).toISOString()
}
}
};
}
/**
* Default test project IDs from config
*/
const DEFAULT_TEST_PROJECT_IDS = [
"test_project_1",
"test_project_2",
"test_project_3",
"demo_project_alpha",
"demo_project_beta"
];
/**
* Generate all test projects
*/
function generateAllTestProjects(projectIds = DEFAULT_TEST_PROJECT_IDS) {
return projectIds.map((handleId, index) =>
generateTestProjectWithClaim(handleId, index)
);
}
/**
* Seed projects to localhost API server
*
* Makes POST requests to create projects in your local API
*/
function seedToLocalhost(apiUrl, projectIds = DEFAULT_TEST_PROJECT_IDS) {
return new Promise((resolve, reject) => {
const projects = generateAllTestProjects(projectIds);
console.log(`📦 Generating ${projects.length} test projects...`);
projects.forEach((project, index) => {
console.log(` ${index + 1}. ${project.planSummary.handleId} - ${project.planSummary.name}`);
});
// If your API has a seed endpoint, use this:
const seedUrl = `${apiUrl}/api/test/seed-projects`;
const postData = JSON.stringify({ projects });
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = http.request(seedUrl, options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode === 200 || res.statusCode === 201) {
console.log('✅ Test projects seeded successfully');
console.log(`📊 Response: ${data}`);
resolve(JSON.parse(data));
} else {
console.error(`❌ Seed failed: ${res.statusCode} ${res.statusMessage}`);
console.error(`Response: ${data}`);
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
}
});
});
req.on('error', (error) => {
console.error(`❌ Request error: ${error.message}`);
reject(error);
});
req.write(postData);
req.end();
});
}
/**
* Generate mock API server response data
*
* Returns the data structure that your localhost API should return
*/
function getMockApiResponse(projectIds = DEFAULT_TEST_PROJECT_IDS, afterId = null) {
const allProjects = generateAllTestProjects(projectIds);
// Filter by afterId if provided (simple comparison by jwtId)
let filteredProjects = allProjects;
if (afterId) {
const afterIndex = allProjects.findIndex(p => p.planSummary.jwtId === afterId);
if (afterIndex >= 0) {
filteredProjects = allProjects.slice(afterIndex + 1);
} else {
// If afterId not found, return all (for testing)
filteredProjects = allProjects;
}
}
const nextAfterId = filteredProjects.length > 0
? filteredProjects[filteredProjects.length - 1].planSummary.jwtId
: null;
return {
data: filteredProjects,
hitLimit: false,
pagination: {
hasMore: false,
nextAfterId: nextAfterId
}
};
}
/**
* Export project data as JSON file
*/
function exportToJSON(filename = 'test-projects.json', projectIds = DEFAULT_TEST_PROJECT_IDS) {
const fs = require('fs');
const projects = generateAllTestProjects(projectIds);
const data = {
projects: projects,
generatedAt: new Date().toISOString(),
count: projects.length
};
fs.writeFileSync(filename, JSON.stringify(data, null, 2));
console.log(`💾 Exported ${projects.length} test projects to ${filename}`);
return data;
}
// CLI usage
if (require.main === module) {
const args = process.argv.slice(2);
const command = args[0];
switch (command) {
case 'export':
{
const filename = args[1] || 'test-projects.json';
const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS;
exportToJSON(filename, projectIds);
}
break;
case 'seed':
{
const apiUrl = args[1] || 'http://localhost:3000';
const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS;
seedToLocalhost(apiUrl, projectIds)
.then(() => {
console.log('✅ Seeding complete');
process.exit(0);
})
.catch((error) => {
console.error('❌ Seeding failed:', error.message);
process.exit(1);
});
}
break;
case 'generate':
default:
{
const projectIds = args[1] ? args[1].split(',') : DEFAULT_TEST_PROJECT_IDS;
const projects = generateAllTestProjects(projectIds);
console.log(JSON.stringify({ data: projects }, null, 2));
}
break;
}
}
module.exports = {
generateTestProject,
generateTestProjectWithClaim,
generateAllTestProjects,
getMockApiResponse,
seedToLocalhost,
exportToJSON,
DEFAULT_TEST_PROJECT_IDS,
generateJwtId
};