- Update plan handle ID format to match TimeSafari specification - Default format: https://endorser.ch/entity/{26-char-ULID} - ULID: 26 characters, Crockford base32 encoded - Validates RFC 3986 URI format - Add ULID generation functions - generateULID() creates 26-character Crockford base32 strings - generateValidPlanHandleId() creates full URI format IDs - Auto-generates valid IDs for testing - Update DEFAULT_TEST_PROJECT_IDS - Now generates valid URI format IDs automatically - Removes placeholder warnings (IDs are now valid format) - Add URI validation to seed scripts - Validates plan IDs match RFC 3986 URI format - Error messages with format examples - Blocks seeding with invalid formats - Update test-user-zero.ts config - Auto-generates valid URI format plan IDs - Clear documentation of required format - Note that real IDs from database should replace test IDs - Update documentation - Document default URI format specification - Explain ULID structure and encoding - Show examples of valid formats This ensures all test project IDs match the actual TimeSafari plan handle ID format, preventing validation errors during prefetch testing.
198 lines
5.9 KiB
JavaScript
Executable File
198 lines
5.9 KiB
JavaScript
Executable File
#!/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);
|
|
});
|
|
|