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

View File

@@ -0,0 +1,170 @@
#!/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) {
seededProjects = generateAllTestProjects(projectIds);
console.log(`🌱 Seeded ${seededProjects.length} test projects:`);
seededProjects.forEach((project, index) => {
console.log(` ${index + 1}. ${project.planSummary.handleId} - ${project.planSummary.name}`);
});
}
/**
* 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
seedProjects(); // 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);
});