From f5dca34e84135c6b117879af128ad67dccdb9f46 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 29 Oct 2025 12:13:59 +0000 Subject: [PATCH] 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. --- docs/localhost-testing-guide.md | 79 ++++++++- scripts/seed-test-projects.js | 249 +++++++++++++++++++++++++++ scripts/test-api-server-with-seed.js | 170 ++++++++++++++++++ 3 files changed, 491 insertions(+), 7 deletions(-) create mode 100755 scripts/seed-test-projects.js create mode 100755 scripts/test-api-server-with-seed.js diff --git a/docs/localhost-testing-guide.md b/docs/localhost-testing-guide.md index 986dd17..7704eb8 100644 --- a/docs/localhost-testing-guide.md +++ b/docs/localhost-testing-guide.md @@ -24,15 +24,39 @@ On Android emulator, `localhost` (127.0.0.1) refers to the **emulator itself**, ### 1. Start Your Local Development Server -Start your TimeSafari API server on your host machine: +You have two options: + +#### Option A: Use the Test API Server (Recommended for Quick Testing) + +The plugin includes a ready-to-use test API server with automatic project seeding: + +```bash +cd /home/matthew/projects/timesafari/daily-notification-plugin +node scripts/test-api-server-with-seed.js [port] +# Default port: 3000 +# Starts on http://localhost:3000 +``` + +This server: +- โœ… Automatically seeds test projects on startup +- โœ… Implements the `/api/v2/report/plansLastUpdatedBetween` endpoint +- โœ… Ready to use immediately with test-project-1, test_project_2, etc. +- โœ… Provides debugging endpoints to view seeded projects + +#### Option B: Use Your Existing TimeSafari API Server + +If you have your own localhost API server, seed test projects into it: ```bash -# Example: If using Node.js/Express -cd /path/to/timesafari-api -npm start -# Server starts on http://localhost:3000 +# Seed projects to your existing API server +node scripts/seed-test-projects.js seed http://localhost:3000 + +# Or export projects as JSON for manual import +node scripts/seed-test-projects.js export test-projects.json ``` +Then ensure your server implements the endpoint as described in the "Localhost API Server Requirements" section below. + ### 2. Configure Test App for Localhost Edit `test-apps/daily-notification-test/src/config/test-user-zero.ts`: @@ -160,9 +184,48 @@ Your localhost API server must implement: - `Content-Type: application/json` - `User-Agent: TimeSafari-DailyNotificationPlugin/1.0.0` -## Quick Test Script +## Seeding Test Projects + +If your localhost API has no projects, you can seed test data using the included scripts: + +### Generate Test Projects + +```bash +# Generate test projects and display as JSON +node scripts/seed-test-projects.js generate -Create a minimal localhost API server for testing: +# Export projects to JSON file +node scripts/seed-test-projects.js export test-projects.json + +# Seed projects directly to your API server +node scripts/seed-test-projects.js seed http://localhost:3000 + +# Use custom project IDs +node scripts/seed-test-projects.js seed http://localhost:3000 "project_1,project_2,project_3" +``` + +The seed script generates test projects matching the structure expected by the plugin, including: +- `handleId` (from your config) +- `jwtId` (timestamp-based for pagination) +- `planSummary` (name, description, dates, location) +- `previousClaim` (for change detection) + +### Using the Test API Server (Includes Seeding) + +The easiest way is to use the included test API server: + +```bash +node scripts/test-api-server-with-seed.js +``` + +This server: +1. Seeds 5 test projects automatically on startup +2. Provides the prefetch endpoint ready to use +3. Includes debugging endpoints + +## Quick Test Script (Alternative) + +If you want to create your own minimal localhost API server: ```javascript // test-api-server.js @@ -217,6 +280,8 @@ Run with: node test-api-server.js ``` +**Note**: The included `scripts/test-api-server-with-seed.js` provides the same functionality plus automatic seeding, so you may prefer to use that instead. + ## Troubleshooting ### Prefetch Not Executing diff --git a/scripts/seed-test-projects.js b/scripts/seed-test-projects.js new file mode 100755 index 0000000..07d4dcd --- /dev/null +++ b/scripts/seed-test-projects.js @@ -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 +}; + diff --git a/scripts/test-api-server-with-seed.js b/scripts/test-api-server-with-seed.js new file mode 100755 index 0000000..cd476e7 --- /dev/null +++ b/scripts/test-api-server-with-seed.js @@ -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); +}); +