You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

197 lines
5.9 KiB

#!/usr/bin/env node
/**
* Test API Server with Project Seeding
*
* A simple Express server that:
* 1. Provides the plansLastUpdatedBetween endpoint for prefetch testing
* 2. Automatically seeds test projects on startup
* 3. Returns seeded project data in API responses
*
* Usage:
* node scripts/test-api-server-with-seed.js [port]
*
* Then in Android emulator, configure:
* api.serverMode = "localhost"
* api.servers.localhost.android = "http://10.0.2.2:3000"
*
* @author Matthew Raymer
* @version 1.0.0
*/
const express = require('express');
const { generateAllTestProjects, getMockApiResponse, DEFAULT_TEST_PROJECT_IDS } = require('./seed-test-projects');
const PORT = process.argv[2] || 3000;
const app = express();
// Store seeded projects in memory
let seededProjects = [];
// Middleware
app.use(express.json());
app.use((req, res, next) => {
console.log(`📥 ${req.method} ${req.path}`);
next();
});
/**
* Seed test projects on startup
*/
function seedProjects(projectIds = DEFAULT_TEST_PROJECT_IDS) {
// Validate plan IDs are valid URIs
const invalidIds = projectIds.filter(id => !id.match(/^[A-Za-z][A-Za-z0-9+.-]+:/));
if (invalidIds.length > 0) {
console.error('');
console.error('❌ ERROR: Invalid plan handle ID format!');
console.error(' Plan handle IDs must be valid URIs (RFC 3986).');
console.error(' Expected format: https://endorser.ch/entity/{ULID}');
console.error(` Invalid IDs: ${invalidIds.join(', ')}`);
console.error('');
console.error(' Valid examples:');
console.error(' https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF');
console.error(' http://example.com/project/123');
console.error(' did:ethr:0x1234...');
console.error('');
process.exit(1);
}
seededProjects = generateAllTestProjects(projectIds);
console.log(`🌱 Seeded ${seededProjects.length} test projects with valid URI format:`);
seededProjects.forEach((project, index) => {
console.log(` ${index + 1}. ${project.planSummary.handleId} - ${project.planSummary.name}`);
});
console.log('');
console.log('📝 Note: These are auto-generated test IDs.');
console.log(' For testing with real projects, use IDs from your TimeSafari database.');
console.log(' Update: test-apps/daily-notification-test/src/config/test-user-zero.ts');
console.log('');
}
/**
* POST /api/v2/report/plansLastUpdatedBetween
*
* Endpoint that the plugin calls for starred projects prefetch
*/
app.post('/api/v2/report/plansLastUpdatedBetween', (req, res) => {
const { planIds, afterId } = req.body;
console.log('📥 Prefetch request received:', {
planIds: planIds || 'not provided',
afterId: afterId || 'none',
authorization: req.headers.authorization ? 'present' : 'missing',
timestamp: new Date().toISOString()
});
// Filter seeded projects to only those requested
let filteredProjects = seededProjects;
if (planIds && Array.isArray(planIds) && planIds.length > 0) {
filteredProjects = seededProjects.filter(p =>
planIds.includes(p.planSummary.handleId)
);
console.log(` Filtered to ${filteredProjects.length} projects matching planIds`);
}
// Filter by afterId if provided
if (afterId) {
const afterIndex = filteredProjects.findIndex(p => p.planSummary.jwtId === afterId);
if (afterIndex >= 0) {
filteredProjects = filteredProjects.slice(afterIndex + 1);
console.log(` Filtered to ${filteredProjects.length} projects after ${afterId}`);
}
}
// Generate response
const response = {
data: filteredProjects,
hitLimit: false,
pagination: {
hasMore: false,
nextAfterId: filteredProjects.length > 0
? filteredProjects[filteredProjects.length - 1].planSummary.jwtId
: null
}
};
console.log(`📤 Sending ${response.data.length} projects in response`);
res.json(response);
});
/**
* GET /api/test/projects
*
* View all seeded projects (for debugging)
*/
app.get('/api/test/projects', (req, res) => {
res.json({
projects: seededProjects,
count: seededProjects.length,
timestamp: new Date().toISOString()
});
});
/**
* POST /api/test/seed-projects
*
* Reseed test projects (useful for resetting)
*/
app.post('/api/test/seed-projects', (req, res) => {
const { projectIds } = req.body;
const idsToUse = projectIds || DEFAULT_TEST_PROJECT_IDS;
seedProjects(idsToUse);
res.json({
success: true,
message: `Seeded ${seededProjects.length} test projects`,
count: seededProjects.length,
projectIds: idsToUse
});
});
/**
* GET /api/test/health
*
* Health check endpoint
*/
app.get('/api/test/health', (req, res) => {
res.json({
status: 'ok',
server: 'test-api-server-with-seed',
port: PORT,
projectsSeeded: seededProjects.length,
timestamp: new Date().toISOString()
});
});
// Start server
// Allow plan IDs to be passed as command line argument
const planIdsArg = process.argv[3];
const planIdsToUse = planIdsArg ? planIdsArg.split(',') : DEFAULT_TEST_PROJECT_IDS;
seedProjects(planIdsToUse); // Seed on startup
app.listen(PORT, () => {
console.log('');
console.log('🧪 Test API Server with Project Seeding');
console.log('========================================');
console.log(`📡 Server running on http://localhost:${PORT}`);
console.log(`📱 Android emulator URL: http://10.0.2.2:${PORT}`);
console.log('');
console.log('Endpoints:');
console.log(` POST /api/v2/report/plansLastUpdatedBetween - Main prefetch endpoint`);
console.log(` GET /api/test/projects - View all seeded projects`);
console.log(` POST /api/test/seed-projects - Reseed projects`);
console.log(` GET /api/test/health - Health check`);
console.log('');
console.log('✅ Ready for prefetch testing!');
console.log('');
});
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\n👋 Shutting down test API server...');
process.exit(0);
});