diff --git a/docs/localhost-testing-guide.md b/docs/localhost-testing-guide.md new file mode 100644 index 0000000..986dd17 --- /dev/null +++ b/docs/localhost-testing-guide.md @@ -0,0 +1,293 @@ +# Localhost Testing Guide + +**Author**: Matthew Raymer +**Version**: 1.0.0 +**Status**: Testing Setup + +## Overview + +This guide explains how to test the daily notification plugin's prefetch functionality with a localhost development API server running on your host machine. + +## Android Emulator Localhost Access + +On Android emulator, `localhost` (127.0.0.1) refers to the **emulator itself**, not your host machine. To access your host machine's localhost from the Android emulator, use: + +**`10.0.2.2`** - Special alias that maps to host machine's localhost + +### Platform-Specific Localhost Addresses + +- **Android Emulator**: `http://10.0.2.2:PORT` +- **iOS Simulator**: `http://localhost:PORT` or `http://127.0.0.1:PORT` +- **Web Browser**: `http://localhost:PORT` + +## Setup Steps + +### 1. Start Your Local Development Server + +Start your TimeSafari API server on your host machine: + +```bash +# Example: If using Node.js/Express +cd /path/to/timesafari-api +npm start +# Server starts on http://localhost:3000 +``` + +### 2. Configure Test App for Localhost + +Edit `test-apps/daily-notification-test/src/config/test-user-zero.ts`: + +```typescript +api: { + serverMode: "localhost", // Change from "mock" to "localhost" + servers: { + localhost: { + android: "http://10.0.2.2:3000", // Match your local server port + ios: "http://localhost:3000", + web: "http://localhost:3000" + }, + // ... other server configs + }, + // ... +}, +testing: { + enableMockResponses: false, // Disable mocks to use real localhost API + // ... +} +``` + +### 3. Update Starred Plans in Test App + +In the test app UI (UserZeroView), use `updateStarredPlans()` to set which project IDs to query: + +```typescript +// In UserZeroView.vue or your test code +await DailyNotification.updateStarredPlans({ + planIds: ["test_project_1", "test_project_2", "test_project_3"] +}); +``` + +### 4. Schedule Notification for Testing + +Schedule a notification that will trigger prefetch 5 minutes before: + +```typescript +await DailyNotification.scheduleDailyNotification({ + time: "11:25", // Current time + 5+ minutes for testing + title: "Test Notification", + body: "Testing prefetch with localhost", + priority: "high" +}); +``` + +The prefetch will run 5 minutes before (11:20 in this example) and query your localhost API. + +## Testing Prefetch Flow + +### Expected Timeline + +1. **T-0**: You schedule notification for `11:25` +2. **T+0**: Plugin schedules prefetch for `11:20` (5 minutes before) +3. **T+0**: Plugin logs: + ``` + DN|SCHEDULE_FETCH_START time=... + DN|WORK_ENQUEUED work_id=... fetch_at=... delay_ms=... + ``` +4. **T+~5 minutes**: WorkManager triggers prefetch at `11:20` +5. **T+~5 minutes**: Plugin queries `http://10.0.2.2:3000/api/v2/report/plansLastUpdatedBetween` +6. **T+~10 minutes**: Notification displays at `11:25` + +### Monitoring Prefetch + +Watch logs for prefetch execution: + +```bash +# Filter for prefetch-related logs +adb logcat | grep -E "DN|SCHEDULE_FETCH|FETCH_PROJECT|WORK_ENQUEUED" + +# Watch WorkManager queue +adb shell dumpsys jobscheduler | grep daily_notification + +# Monitor network requests (if your server logs them) +# Check your localhost server logs for POST requests to /api/v2/report/plansLastUpdatedBetween +``` + +## Localhost API Server Requirements + +Your localhost API server must implement: + +### Endpoint: `POST /api/v2/report/plansLastUpdatedBetween` + +**Request:** +```json +{ + "planIds": ["test_project_1", "test_project_2"], + "afterId": "optional_jwt_id_for_pagination" +} +``` + +**Response:** +```json +{ + "data": [ + { + "planSummary": { + "jwtId": "jwt_id_1", + "handleId": "test_project_1", + "name": "Test Project 1", + "description": "Project description", + "issuerDid": "did:key:...", + "agentDid": "did:key:...", + "startTime": "2025-01-01T00:00:00Z", + "endTime": "2025-01-31T23:59:59Z" + }, + "previousClaim": { + "jwtId": "previous_jwt_id", + "claimType": "project_update" + } + } + ], + "hitLimit": false, + "pagination": { + "hasMore": false, + "nextAfterId": null + } +} +``` + +**Headers Required:** +- `Authorization: Bearer ` +- `Content-Type: application/json` +- `User-Agent: TimeSafari-DailyNotificationPlugin/1.0.0` + +## Quick Test Script + +Create a minimal localhost API server for testing: + +```javascript +// test-api-server.js +const express = require('express'); +const app = express(); + +app.use(express.json()); + +app.post('/api/v2/report/plansLastUpdatedBetween', (req, res) => { + console.log('๐Ÿ“ฅ Prefetch request received:', { + planIds: req.body.planIds, + afterId: req.body.afterId, + headers: req.headers + }); + + // Return mock response + res.json({ + data: [ + { + planSummary: { + jwtId: `${Date.now()}_test_123`, + handleId: req.body.planIds[0] || "test_project", + name: "Localhost Test Project", + description: "Testing prefetch from localhost", + issuerDid: "did:test:issuer", + agentDid: "did:test:agent", + startTime: new Date().toISOString(), + endTime: new Date(Date.now() + 86400000).toISOString() + }, + previousClaim: { + jwtId: "previous_test_jwt", + claimType: "project_update" + } + } + ], + hitLimit: false, + pagination: { + hasMore: false, + nextAfterId: null + } + }); +}); + +app.listen(3000, () => { + console.log('๐Ÿงช Test API server running on http://localhost:3000'); + console.log('๐Ÿ“ก Android emulator should use: http://10.0.2.2:3000'); +}); +``` + +Run with: +```bash +node test-api-server.js +``` + +## Troubleshooting + +### Prefetch Not Executing + +1. **Check WorkManager queue:** + ```bash + adb shell dumpsys jobscheduler | grep -A 20 "daily_notification" + ``` + +2. **Verify prefetch was scheduled:** + ```bash + adb logcat | grep "DN|SCHEDULE_FETCH" + ``` + +3. **Check if WorkManager has constraints:** + - Battery optimization might be blocking execution + - Network might not be available + - Device might be in deep sleep + +### Cannot Connect to Localhost + +1. **Android Emulator**: Use `10.0.2.2` not `localhost` or `127.0.0.1` +2. **Firewall**: Check if your firewall is blocking port 3000 +3. **Server not running**: Verify server is listening on correct port +4. **Wrong port**: Match the port in config with your server's port + +### Network Timeout + +Increase timeout in config: + +```typescript +testing: { + timeoutMs: 60000, // Increase from 30000 to 60000 +} +``` + +### Authentication Errors + +Your localhost server should accept the JWT token generated by the plugin. For testing, you can: + +1. **Disable JWT validation** in your localhost server +2. **Use a test JWT secret** that matches plugin config +3. **Log the JWT** in your server to see what's being sent + +## Switching Between Mock and Localhost + +You can switch modes without rebuilding: + +1. **Mock Mode** (offline testing): + ```typescript + api.serverMode = "mock"; + testing.enableMockResponses = true; + ``` + +2. **Localhost Mode** (real API calls to localhost): + ```typescript + api.serverMode = "localhost"; + testing.enableMockResponses = false; + ``` + +3. **Staging Mode** (real API calls to staging): + ```typescript + api.serverMode = "staging"; + testing.enableMockResponses = false; + ``` + +## Next Steps + +- Test prefetch execution with real localhost API +- Monitor network traffic to verify requests +- Test with different starred plan IDs +- Verify prefetch timing (5 minutes before notification) +- Test notification delivery after prefetch completes + diff --git a/test-apps/daily-notification-test/capacitor.config.ts b/test-apps/daily-notification-test/capacitor.config.ts index c34b641..02c79d8 100644 --- a/test-apps/daily-notification-test/capacitor.config.ts +++ b/test-apps/daily-notification-test/capacitor.config.ts @@ -18,7 +18,8 @@ const config: CapacitorConfig = { timesafariConfig: { activeDid: TEST_USER_ZERO_CONFIG.identity.did, endpoints: { - projectsLastUpdated: `${TEST_USER_ZERO_CONFIG.api.server}${TEST_USER_ZERO_CONFIG.api.starsEndpoint}` + // Use getApiServerUrl() to get correct URL based on serverMode + projectsLastUpdated: `${TEST_USER_ZERO_CONFIG.getApiServerUrl()}${TEST_USER_ZERO_CONFIG.api.starsEndpoint}` }, starredProjectsConfig: { enabled: true, diff --git a/test-apps/daily-notification-test/src/config/test-user-zero.ts b/test-apps/daily-notification-test/src/config/test-user-zero.ts index 74dd2bf..84b9457 100644 --- a/test-apps/daily-notification-test/src/config/test-user-zero.ts +++ b/test-apps/daily-notification-test/src/config/test-user-zero.ts @@ -19,8 +19,27 @@ export const TEST_USER_ZERO_CONFIG = { // API Configuration api: { - // Use staging API server for testing - server: "https://api-staging.timesafari.com", + // API server selection: + // - "localhost": Use 10.0.2.2:PORT (Android emulator special alias for host localhost) + // - "staging": Use staging API server + // - "production": Use production API server + // - "mock": Use mock responses (no network calls) + // - "custom": Use servers.custom URL + serverMode: "mock" as "localhost" | "staging" | "production" | "mock" | "custom", + + // Server URLs for different modes + servers: { + // Android emulator: use 10.0.2.2 to access host machine's localhost + // iOS simulator: use localhost or 127.0.0.1 + localhost: { + android: "http://10.0.2.2:3000", // Default local dev server port + ios: "http://localhost:3000", + web: "http://localhost:3000" + }, + staging: "https://api-staging.timesafari.com", + production: "https://api.timesafari.com", + custom: "" // Set this if using custom URL + }, // Stars querying endpoint (from crowd-master endorserServer.ts) starsEndpoint: "/api/v2/report/plansLastUpdatedBetween", @@ -29,6 +48,36 @@ export const TEST_USER_ZERO_CONFIG = { jwtExpirationMinutes: 1, // Short-lived tokens like crowd-master jwtAlgorithm: "HS256" }, + + /** + * Get current API server URL based on mode and platform + * Call this function instead of accessing api.server directly + */ + getApiServerUrl(): string { + if (TEST_USER_ZERO_CONFIG.api.serverMode === "mock") { + return "mock://localhost"; // Not used, but needed for type checking + } + if (TEST_USER_ZERO_CONFIG.api.serverMode === "localhost") { + // Detect platform and use appropriate localhost address + if (typeof window !== "undefined") { + const isAndroid = /Android/i.test(navigator.userAgent); + const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); + + if (isAndroid) { + return TEST_USER_ZERO_CONFIG.api.servers.localhost.android; + } else if (isIOS) { + return TEST_USER_ZERO_CONFIG.api.servers.localhost.ios; + } else { + return TEST_USER_ZERO_CONFIG.api.servers.localhost.web; + } + } + return TEST_USER_ZERO_CONFIG.api.servers.localhost.android; // Default to Android for Capacitor + } + if (TEST_USER_ZERO_CONFIG.api.serverMode === "custom" && TEST_USER_ZERO_CONFIG.api.servers.custom) { + return TEST_USER_ZERO_CONFIG.api.servers.custom; + } + return TEST_USER_ZERO_CONFIG.api.servers[TEST_USER_ZERO_CONFIG.api.serverMode] as string; + }, // Test Starred Projects (mock data for testing) starredProjects: { @@ -61,8 +110,23 @@ export const TEST_USER_ZERO_CONFIG = { // Testing Configuration testing: { // Enable mock responses for offline testing + // When true, API calls return mock data instead of making network requests enableMockResponses: true, + // Localhost testing configuration + localhost: { + // Port your local dev server runs on + port: 3000, + + // Enable HTTPS for localhost (requires self-signed cert in emulator) + useHttps: false, + + // Additional headers for localhost requests + headers: { + "X-Development-Mode": "true" + } + }, + // Network timeouts timeoutMs: 30000, retryAttempts: 3, @@ -160,11 +224,22 @@ export class TestUserZeroAPI { private baseUrl: string; private jwt: string; - constructor(baseUrl: string = TEST_USER_ZERO_CONFIG.api.server) { - this.baseUrl = baseUrl; + constructor(baseUrl?: string) { + // Use provided URL, or get from config based on serverMode + this.baseUrl = baseUrl || (TEST_USER_ZERO_CONFIG.api.serverMode === "mock" + ? "mock://localhost" + : TEST_USER_ZERO_CONFIG.getApiServerUrl()); this.jwt = generateTestJWT(); } + /** + * Set API server URL (useful for switching between localhost, staging, etc.) + */ + setBaseUrl(url: string): void { + this.baseUrl = url; + console.log("๐Ÿ”ง API base URL updated to:", url); + } + /** * Query starred projects for changes * Mimics crowd-master getStarredProjectsWithChanges function @@ -173,15 +248,19 @@ export class TestUserZeroAPI { starredPlanIds: string[], afterId?: string ): Promise { - if (TEST_USER_ZERO_CONFIG.testing.enableMockResponses) { + // Check if we should use mock responses + const useMock = TEST_USER_ZERO_CONFIG.testing.enableMockResponses || + TEST_USER_ZERO_CONFIG.api.serverMode === "mock"; + + if (useMock) { // Return mock data for offline testing - console.log("๐Ÿงช Using mock starred projects response"); return MOCK_STARRED_PROJECTS_RESPONSE; } // Real API call (when mock is disabled) - const url = `${this.baseUrl}${TEST_USER_ZERO_CONFIG.api.starsEndpoint}`; + const baseUrl = this.baseUrl || TEST_USER_ZERO_CONFIG.getApiServerUrl(); + const url = `${baseUrl}${TEST_USER_ZERO_CONFIG.api.starsEndpoint}`; const headers = { 'Authorization': `Bearer ${this.jwt}`, 'Content-Type': 'application/json',