Browse Source

feat(test): add localhost testing support for prefetch

- Add serverMode configuration to test-user-zero config
  - Supports: localhost, staging, production, mock, custom
  - Auto-detects platform (Android/iOS/Web) for localhost URLs
  - Android emulator uses 10.0.2.2 for host machine localhost

- Add getApiServerUrl() helper function
  - Returns correct URL based on serverMode and platform
  - Handles Android emulator special case (10.0.2.2)

- Update TestUserZeroAPI to respect serverMode
  - Checks mock mode before making network calls
  - Uses getApiServerUrl() for base URL resolution
  - Allows runtime URL switching via setBaseUrl()

- Add localhost testing configuration
  - Configurable port and HTTPS settings
  - Development mode headers support

- Create localhost testing guide
  - Step-by-step setup instructions
  - Platform-specific localhost addresses explained
  - Quick test API server example
  - Troubleshooting common issues
  - Monitoring prefetch execution commands

- Update Capacitor config to use getApiServerUrl()
  - Fixes breaking change from api.server removal

This enables testing prefetch functionality with a local development
API server running on localhost, perfect for development and debugging
of the 5-minute prefetch scheduling feature.
master
Matthew Raymer 3 days ago
parent
commit
1bf39fd1f7
  1. 293
      docs/localhost-testing-guide.md
  2. 3
      test-apps/daily-notification-test/capacitor.config.ts
  3. 93
      test-apps/daily-notification-test/src/config/test-user-zero.ts

293
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 <JWT_TOKEN>`
- `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

3
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,

93
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",
@ -30,6 +49,36 @@ export const TEST_USER_ZERO_CONFIG = {
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: {
// Sample starred project IDs for testing
@ -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<typeof MOCK_STARRED_PROJECTS_RESPONSE> {
if (TEST_USER_ZERO_CONFIG.testing.enableMockResponses) {
// Return mock data for offline testing
// 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',

Loading…
Cancel
Save