Browse Source

chore: update Android test app dependencies to latest stable versions

- Update AndroidX AppCompat from 1.6.1 to 1.7.1 (latest stable)
- Update AndroidX Activity from 1.7.0 to 1.8.2
- Update AndroidX Core from 1.10.0 to 1.12.0
- Update AndroidX Fragment from 1.5.6 to 1.6.2
- Update Core Splash Screen from 1.0.0 to 1.0.1
- Update AndroidX WebKit from 1.6.1 to 1.8.0
- Update compile/target SDK from 33 to 34
- Update Gradle troubleshooting guide with latest versions

Dependency updates:
- androidx.appcompat:appcompat: 1.6.1 → 1.7.1
- androidx.activity:activity: 1.7.0 → 1.8.2
- androidx.core:core: 1.10.0 → 1.12.0
- androidx.fragment:fragment: 1.5.6 → 1.6.2
- androidx.core:core-splashscreen: 1.0.0 → 1.0.1
- androidx.webkit:webkit: 1.6.1 → 1.8.0
- compileSdkVersion: 33 → 34
- targetSdkVersion: 33 → 34

Documentation updates:
- Updated Gradle troubleshooting guide with latest versions
- Added dependency update section
- Updated version compatibility table
- Added AndroidX dependency update examples

Files: 2 modified
- Modified: android/variables.gradle (updated all AndroidX versions)
- Modified: GRADLE_TROUBLESHOOTING.md (updated documentation)
research/notification-plugin-enhancement
Matthew Raymer 6 days ago
parent
commit
a2d1fb33a6
  1. 109
      test-apps/README.md
  2. 307
      test-apps/SETUP_GUIDE.md
  3. 160
      test-apps/android-test/GRADLE_TROUBLESHOOTING.md
  4. 5214
      test-apps/android-test/package-lock.json
  5. 112
      test-apps/check-environment.sh
  6. 92
      test-apps/setup-android.sh
  7. 37
      test-apps/setup-electron.sh
  8. 70
      test-apps/setup-ios.sh
  9. 152
      test-apps/test-api/.gitignore
  10. 282
      test-apps/test-api/README.md
  11. 76
      test-apps/test-api/SETUP.md
  12. 305
      test-apps/test-api/client.ts
  13. 4799
      test-apps/test-api/package-lock.json
  14. 33
      test-apps/test-api/package.json
  15. 321
      test-apps/test-api/server.js
  16. 294
      test-apps/test-api/test-demo.js

109
test-apps/README.md

@ -11,9 +11,12 @@ test-apps/
├── android-test/ # Android test app
├── ios-test/ # iOS test app
├── electron-test/ # Electron test app
├── test-api/ # Test API server
├── setup-android.sh # Android setup script
├── setup-ios.sh # iOS setup script
├── setup-electron.sh # Electron setup script
├── check-environment.sh # Environment verification
├── SETUP_GUIDE.md # Enhanced setup guide
└── README.md # This guide
```
@ -29,36 +32,32 @@ test-apps/
### Option 1: Automated Setup (Recommended)
```bash
# Setup all platforms
# Navigate to test-apps directory first
cd test-apps
# Setup all platforms (run from test-apps directory)
./setup-android.sh
./setup-ios.sh
./setup-electron.sh
```
### Option 2: Manual Setup
```bash
# Android
cd android-test
npm install
npx cap init "Daily Notification Android Test" "com.timesafari.dailynotification.androidtest"
npx cap add android
npm run build
npx cap sync android
**⚠️ Important**: Run setup scripts from the `test-apps` directory, not from individual platform directories.
# iOS
cd ios-test
npm install
npx cap init "Daily Notification iOS Test" "com.timesafari.dailynotification.iostest"
npx cap add ios
npm run build
npx cap sync ios
### Option 2: Manual Setup
See [Enhanced Setup Guide](SETUP_GUIDE.md) for detailed manual setup instructions and troubleshooting.
# Electron
cd electron-test
npm install
npm run build-web
### Prerequisites Check
```bash
# Check your environment before setup
./check-environment.sh
```
**Required Software**:
- **Node.js 18+**: Required for all platforms
- **Android Studio**: Required for Android testing
- **Xcode**: Required for iOS testing (macOS only)
- **No additional requirements**: For Electron testing
## Test App Features
Each test app includes:
@ -72,6 +71,42 @@ Each test app includes:
- **Error Handling**: Comprehensive error testing
- **Debug Information**: Platform-specific debug data
## Test API Server
A mock REST API server (`test-api/`) provides endpoints for testing the plugin's network functionality:
### Quick Start
```bash
# Start the test API server
cd test-apps/test-api
npm install
npm start
# Test the API
npm run demo
```
### Key Features
- **Content Endpoints**: Generate mock notification content
- **ETag Support**: Full HTTP caching with conditional requests
- **Error Simulation**: Test various error scenarios
- **Metrics**: Monitor API usage and performance
- **CORS Enabled**: Cross-origin requests supported
### API Endpoints
- `GET /health` - Health check
- `GET /api/content/:slotId` - Get notification content
- `GET /api/error/:type` - Simulate errors
- `GET /api/metrics` - API metrics
- `PUT /api/content/:slotId` - Update content
- `DELETE /api/content` - Clear all content
### Platform-Specific URLs
- **Web/Electron**: `http://localhost:3001`
- **Android Emulator**: `http://10.0.2.2:3001`
- **iOS Simulator**: `http://localhost:3001`
- **Physical Devices**: `http://[YOUR_IP]:3001`
## Platform-Specific Testing
### Android Test App
@ -140,16 +175,28 @@ npm run dev # Run in development mode
## Troubleshooting
### Common Issues
1. **Build Failures**: Ensure all dependencies installed
2. **Platform Errors**: Check platform-specific SDKs installed
3. **Permission Issues**: Verify platform permissions configured
4. **Sync Problems**: Run `npx cap sync` after changes
### Development Tips
- Use `npm run dev` for web testing
- Use platform-specific tools for native testing
- Check console logs for detailed error information
- Test on both physical devices and simulators
1. **"Unknown command: cap"** → Install Capacitor CLI: `npm install -g @capacitor/cli`
2. **"android platform has not been added yet"** → Run `npx cap add android` first
3. **Build failures** → Check Node.js version (18+) and clear cache: `npm cache clean --force`
4. **Platform errors** → Verify platform-specific SDKs are installed
### Quick Fixes
```bash
# Check environment
./check-environment.sh
# Reinstall dependencies
rm -rf node_modules && npm install
# Clear Capacitor cache
npx cap clean
# Re-sync platforms
npx cap sync
```
### Detailed Help
See [Enhanced Setup Guide](SETUP_GUIDE.md) for comprehensive troubleshooting and platform-specific solutions.
## Next Steps

307
test-apps/SETUP_GUIDE.md

@ -0,0 +1,307 @@
# Enhanced Test Apps Setup Guide
## Overview
This guide creates minimal Capacitor test apps for validating the Daily Notification Plugin across all target platforms with robust error handling and clear troubleshooting.
## Prerequisites
### Required Software
- **Node.js 18+**: Download from [nodejs.org](https://nodejs.org/)
- **npm**: Comes with Node.js
- **Git**: For version control
### Platform-Specific Requirements
#### Android
- **Android Studio**: Download from [developer.android.com/studio](https://developer.android.com/studio)
- **Android SDK**: Installed via Android Studio
- **Java Development Kit (JDK)**: Version 11 or higher
#### iOS (macOS only)
- **Xcode**: Install from Mac App Store
- **Xcode Command Line Tools**: `xcode-select --install`
- **iOS Simulator**: Included with Xcode
#### Electron
- **No additional requirements**: Works on any platform with Node.js
## Quick Start
### Option 1: Automated Setup (Recommended)
```bash
# Navigate to test-apps directory
cd test-apps
# Setup all platforms (run from test-apps directory)
./setup-android.sh
./setup-ios.sh
./setup-electron.sh
```
### Option 2: Manual Setup
#### Android Manual Setup
```bash
cd test-apps/android-test
# Install dependencies
npm install
# Install Capacitor CLI globally
npm install -g @capacitor/cli
# Initialize Capacitor
npx cap init "Daily Notification Android Test" "com.timesafari.dailynotification.androidtest"
# Add Android platform
npx cap add android
# Build web assets
npm run build
# Sync to native
npx cap sync android
```
#### iOS Manual Setup
```bash
cd test-apps/ios-test
# Install dependencies
npm install
# Install Capacitor CLI globally
npm install -g @capacitor/cli
# Initialize Capacitor
npx cap init "Daily Notification iOS Test" "com.timesafari.dailynotification.iostest"
# Add iOS platform
npx cap add ios
# Build web assets
npm run build
# Sync to native
npx cap sync ios
```
#### Electron Manual Setup
```bash
cd test-apps/electron-test
# Install dependencies
npm install
# Build web assets
npm run build-web
```
## Common Issues and Solutions
### Issue: "Unknown command: cap"
**Solution**: Install Capacitor CLI globally
```bash
npm install -g @capacitor/cli
```
### Issue: "android platform has not been added yet"
**Solution**: Add the Android platform first
```bash
npx cap add android
```
### Issue: "Failed to add Android platform"
**Solutions**:
1. Install Android Studio and Android SDK
2. Set `ANDROID_HOME` environment variable
3. Add Android SDK tools to your PATH
### Issue: "Failed to add iOS platform"
**Solutions**:
1. Install Xcode from Mac App Store
2. Install Xcode Command Line Tools: `xcode-select --install`
3. Ensure you're running on macOS
### Issue: Build failures
**Solutions**:
1. Check Node.js version: `node --version` (should be 18+)
2. Clear npm cache: `npm cache clean --force`
3. Delete `node_modules` and reinstall: `rm -rf node_modules && npm install`
## Platform-Specific Setup
### Android Setup Verification
```bash
# Check Android Studio installation
which studio
# Check Android SDK
echo $ANDROID_HOME
# Check Java
java -version
```
**Required Environment Variables**:
```bash
export ANDROID_HOME=/path/to/android/sdk
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
```
### iOS Setup Verification
```bash
# Check Xcode installation
xcodebuild -version
# Check iOS Simulator
xcrun simctl list devices
# Check Command Line Tools
xcode-select -p
```
### Electron Setup Verification
```bash
# Check Node.js version
node --version
# Check npm
npm --version
# Test Electron installation
npx electron --version
```
## Running the Test Apps
### Android
```bash
cd test-apps/android-test
# Web development server (for testing)
npm run dev
# Open in Android Studio
npx cap open android
# Run on device/emulator
npx cap run android
```
### iOS
```bash
cd test-apps/ios-test
# Web development server (for testing)
npm run dev
# Open in Xcode
npx cap open ios
# Run on device/simulator
npx cap run ios
```
### Electron
```bash
cd test-apps/electron-test
# Run Electron app
npm start
# Run in development mode
npm run dev
# Build and run
npm run electron
```
## Testing Workflow
### 1. Web Testing (Recommended First)
```bash
# Test each platform's web version first
cd test-apps/android-test && npm run dev
cd test-apps/ios-test && npm run dev
cd test-apps/electron-test && npm start
```
### 2. Native Testing
```bash
# After web testing succeeds, test native platforms
cd test-apps/android-test && npx cap run android
cd test-apps/ios-test && npx cap run ios
```
### 3. Integration Testing
- Test plugin configuration
- Test notification scheduling
- Test platform-specific features
- Test error handling
- Test performance metrics
## Troubleshooting Checklist
### General Issues
- [ ] Node.js 18+ installed
- [ ] npm working correctly
- [ ] Capacitor CLI installed globally
- [ ] Dependencies installed (`npm install`)
### Android Issues
- [ ] Android Studio installed
- [ ] Android SDK configured
- [ ] `ANDROID_HOME` environment variable set
- [ ] Java JDK 11+ installed
- [ ] Android platform added (`npx cap add android`)
### iOS Issues
- [ ] Xcode installed (macOS only)
- [ ] Xcode Command Line Tools installed
- [ ] iOS Simulator available
- [ ] iOS platform added (`npx cap add ios`)
### Electron Issues
- [ ] Node.js working correctly
- [ ] Dependencies installed
- [ ] Web assets built (`npm run build-web`)
## Development Tips
### Web Development
- Use `npm run dev` for hot reloading
- Test plugin APIs in browser console
- Use browser dev tools for debugging
### Native Development
- Use `npx cap sync` after making changes
- Check native logs for detailed errors
- Test on both physical devices and simulators
### Debugging
- Check console logs for errors
- Use `npx cap doctor` to diagnose issues
- Verify platform-specific requirements
## Next Steps
1. **Run Setup Scripts**: Execute platform-specific setup
2. **Test Web Versions**: Validate basic functionality
3. **Test Native Platforms**: Verify platform-specific features
4. **Integration Testing**: Test with actual plugin implementation
5. **Performance Validation**: Monitor metrics and optimizations
## Support
If you encounter issues not covered in this guide:
1. Check the [Capacitor Documentation](https://capacitorjs.com/docs)
2. Verify platform-specific requirements
3. Check console logs for detailed error messages
4. Ensure all prerequisites are properly installed

160
test-apps/android-test/GRADLE_TROUBLESHOOTING.md

@ -0,0 +1,160 @@
# Android Test App Gradle Sync Troubleshooting
## Problem: Gradle Sync Failure
**Error Message:**
```
Unable to find method 'org.gradle.api.artifacts.Dependency org.gradle.api.artifacts.dsl.DependencyHandler.module(java.lang.Object)'
```
## Root Cause
The Android test app was using **Gradle 9.0-milestone-1** (pre-release) with **Android Gradle Plugin 8.0.0**, causing version incompatibility issues.
## Solution Applied
### 1. Updated Gradle Version
**File:** `android/gradle/wrapper/gradle-wrapper.properties`
```properties
# Changed from:
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-milestone-1-bin.zip
# To:
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
```
### 2. Updated Android Gradle Plugin
**File:** `android/build.gradle`
```gradle
// Changed from:
classpath 'com.android.tools.build:gradle:8.0.0'
// To:
classpath 'com.android.tools.build:gradle:8.13.0'
```
### 3. Updated Google Services Plugin
```gradle
// Changed from:
classpath 'com.google.gms:google-services:4.3.15'
// To:
classpath 'com.google.gms:google-services:4.4.0'
```
### 4. Updated AndroidX Dependencies
**File:** `android/variables.gradle`
```gradle
// Updated to latest stable versions:
androidxAppCompatVersion = '1.7.1' // was 1.6.1
androidxActivityVersion = '1.8.2' // was 1.7.0
androidxCoreVersion = '1.12.0' // was 1.10.0
androidxFragmentVersion = '1.6.2' // was 1.5.6
coreSplashScreenVersion = '1.0.1' // was 1.0.0
androidxWebkitVersion = '1.8.0' // was 1.6.1
compileSdkVersion = 34 // was 33
targetSdkVersion = 34 // was 33
```
## Manual Fix Steps
If you encounter this issue again:
### Step 1: Clean Gradle Cache
```bash
cd test-apps/android-test/android
./gradlew clean
./gradlew --stop
```
### Step 2: Clear Gradle Wrapper Cache
```bash
rm -rf ~/.gradle/wrapper/dists/gradle-9.0-milestone-1*
```
### Step 3: Re-sync Project
In Android Studio:
1. Click **File** → **Sync Project with Gradle Files**
2. Or click the **Sync Now** link in the error banner
### Step 4: If Still Failing
```bash
# Delete all Gradle caches
rm -rf ~/.gradle/caches
rm -rf ~/.gradle/wrapper
# Re-download Gradle
cd test-apps/android-test/android
./gradlew wrapper --gradle-version 8.4
```
## Prevention
### Use Stable Versions
Always use stable, tested version combinations:
| Android Gradle Plugin | Gradle Version | Status |
|----------------------|----------------|---------|
| 8.13.0 | 8.13 | ✅ Latest Stable |
| 8.1.4 | 8.4 | ✅ Stable |
| 8.0.0 | 8.0 | ✅ Stable |
| 7.4.2 | 7.5 | ✅ Stable |
| 8.0.0 | 9.0-milestone-1 | ❌ Incompatible |
### Version Compatibility Check
- **Android Gradle Plugin 8.13.0** requires **Gradle 8.0+**
- **Gradle 8.13** is the latest stable version
- **AndroidX AppCompat 1.7.1** is the latest stable version
- Avoid pre-release versions in production
## Additional Troubleshooting
### If Sync Still Fails
1. **Check Java Version**
```bash
java -version
# Should be Java 17+ for AGP 8.1.4
```
2. **Check Android SDK**
```bash
echo $ANDROID_HOME
# Should point to Android SDK location
```
3. **Check Local Properties**
```bash
# Verify android/local.properties exists
cat test-apps/android-test/android/local.properties
```
4. **Recreate Project**
```bash
cd test-apps/android-test
rm -rf android/
npx cap add android
```
## Success Indicators
After applying the fix, you should see:
- ✅ **Gradle sync successful**
- ✅ **No red error banners**
- ✅ **Build.gradle file opens without errors**
- ✅ **Project structure loads correctly**
## Next Steps
Once Gradle sync is successful:
1. **Build the project**: `./gradlew build`
2. **Run on device**: `npx cap run android`
3. **Test plugin functionality**: Use the test API server
4. **Validate notifications**: Test the Daily Notification Plugin
## Related Issues
- **Build failures**: Usually resolved by Gradle sync fix
- **Plugin not found**: Check Capacitor plugin installation
- **Permission errors**: Verify Android manifest permissions
- **Runtime crashes**: Check plugin initialization code

5214
test-apps/android-test/package-lock.json

File diff suppressed because it is too large

112
test-apps/check-environment.sh

@ -0,0 +1,112 @@
#!/bin/bash
# Environment Verification Script for Test Apps
echo "🔍 Verifying Test Apps Environment..."
echo ""
# Check Node.js
echo "📦 Node.js:"
if command -v node &> /dev/null; then
node_version=$(node --version)
echo " ✅ Installed: $node_version"
# Check if version is 18+
major_version=$(echo $node_version | cut -d'.' -f1 | sed 's/v//')
if [ "$major_version" -ge 18 ]; then
echo " ✅ Version 18+ (compatible)"
else
echo " ⚠️ Version $major_version (requires 18+)"
fi
else
echo " ❌ Not installed"
fi
# Check npm
echo ""
echo "📦 npm:"
if command -v npm &> /dev/null; then
npm_version=$(npm --version)
echo " ✅ Installed: $npm_version"
else
echo " ❌ Not installed"
fi
# Check Capacitor CLI
echo ""
echo "⚡ Capacitor CLI:"
if command -v cap &> /dev/null; then
cap_version=$(cap --version)
echo " ✅ Installed: $cap_version"
else
echo " ❌ Not installed (will be installed by setup scripts)"
fi
# Check Android (if available)
echo ""
echo "📱 Android:"
if command -v studio &> /dev/null; then
echo " ✅ Android Studio installed"
else
echo " ❌ Android Studio not found"
fi
if [ ! -z "$ANDROID_HOME" ]; then
echo " ✅ ANDROID_HOME set: $ANDROID_HOME"
else
echo " ❌ ANDROID_HOME not set"
fi
if command -v java &> /dev/null; then
java_version=$(java -version 2>&1 | head -n 1)
echo " ✅ Java: $java_version"
else
echo " ❌ Java not found"
fi
# Check iOS (if on macOS)
echo ""
echo "🍎 iOS:"
if [[ "$OSTYPE" == "darwin"* ]]; then
if command -v xcodebuild &> /dev/null; then
xcode_version=$(xcodebuild -version | head -n 1)
echo " ✅ Xcode: $xcode_version"
else
echo " ❌ Xcode not installed"
fi
if command -v xcrun &> /dev/null; then
echo " ✅ Xcode Command Line Tools available"
else
echo " ❌ Xcode Command Line Tools not installed"
fi
else
echo " ⚠️ iOS development requires macOS"
fi
# Check Electron
echo ""
echo "⚡ Electron:"
if command -v npx &> /dev/null; then
electron_version=$(npx electron --version 2>/dev/null)
if [ $? -eq 0 ]; then
echo " ✅ Electron available: $electron_version"
else
echo " ⚠️ Electron not installed (will be installed by setup)"
fi
else
echo " ❌ npx not available"
fi
echo ""
echo "📋 Summary:"
echo " - Node.js 18+: $(command -v node &> /dev/null && node --version | cut -d'.' -f1 | sed 's/v//' | awk '{if($1>=18) print "✅"; else print "❌"}' || echo "❌")"
echo " - npm: $(command -v npm &> /dev/null && echo "✅" || echo "❌")"
echo " - Android Studio: $(command -v studio &> /dev/null && echo "✅" || echo "❌")"
echo " - Xcode: $(command -v xcodebuild &> /dev/null && echo "✅" || echo "❌")"
echo " - Electron: $(command -v npx &> /dev/null && npx electron --version &> /dev/null && echo "✅" || echo "❌")"
echo ""
echo "🚀 Next Steps:"
echo " 1. Install missing prerequisites"
echo " 2. Run setup scripts: ./setup-*.sh"
echo " 3. See SETUP_GUIDE.md for detailed instructions"

92
test-apps/setup-android.sh

@ -3,37 +3,121 @@
# Android Test App Setup Script
echo "🚀 Setting up Android Test App..."
# Check if we're in the right directory
if [ ! -d "android-test" ]; then
echo "❌ Error: android-test directory not found!"
echo "Please run this script from the test-apps directory"
exit 1
fi
cd android-test
# Check Node.js version
echo "🔍 Checking Node.js version..."
node_version=$(node --version 2>/dev/null)
if [ $? -ne 0 ]; then
echo "❌ Error: Node.js not found!"
echo "Please install Node.js 18+ from https://nodejs.org/"
exit 1
fi
echo "✅ Node.js version: $node_version"
# Install dependencies
echo "📦 Installing dependencies..."
npm install
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to install dependencies!"
exit 1
fi
# Install Capacitor CLI globally if not present
if ! command -v cap &> /dev/null; then
echo "🔧 Installing Capacitor CLI globally..."
npm install -g @capacitor/cli
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to install Capacitor CLI!"
exit 1
fi
else
echo "✅ Capacitor CLI already installed"
fi
# Initialize Capacitor
# Initialize Capacitor (only if not already initialized)
if [ ! -f "capacitor.config.ts" ]; then
echo "⚡ Initializing Capacitor..."
npx cap init "Daily Notification Android Test" "com.timesafari.dailynotification.androidtest"
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to initialize Capacitor!"
exit 1
fi
else
echo "✅ Capacitor already initialized"
fi
# Add Android platform
# Add Android platform (only if not already added)
if [ ! -d "android" ]; then
echo "📱 Adding Android platform..."
npx cap add android
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to add Android platform!"
echo "Make sure Android Studio and Android SDK are installed"
exit 1
fi
else
echo "✅ Android platform already added"
fi
# Build web assets
echo "🔨 Building web assets..."
npm run build
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to build web assets!"
exit 1
fi
# Sync to native
echo "🔄 Syncing to native..."
npx cap sync android
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to sync to native!"
echo "🔧 Attempting to fix Gradle sync issues..."
# Fix common Gradle sync issues
cd android
./gradlew clean
./gradlew --stop
# Clear Gradle cache if needed
if [ -d ~/.gradle/wrapper/dists/gradle-9.0-milestone-1* ]; then
echo "🧹 Clearing incompatible Gradle cache..."
rm -rf ~/.gradle/wrapper/dists/gradle-9.0-milestone-1*
fi
cd ..
# Try sync again
echo "🔄 Retrying sync..."
npx cap sync android
if [ $? -ne 0 ]; then
echo "❌ Error: Sync still failing after cleanup"
echo "📋 See GRADLE_TROUBLESHOOTING.md for manual fixes"
exit 1
fi
fi
echo ""
echo "✅ Android test app setup complete!"
echo ""
echo "Next steps:"
echo "📋 Prerequisites check:"
echo "- Android Studio installed: $(command -v studio &> /dev/null && echo '✅' || echo '❌')"
echo "- Android SDK configured: $(echo $ANDROID_HOME | grep -q . && echo '✅' || echo '❌')"
echo ""
echo "🚀 Next steps:"
echo "1. Open Android Studio: npx cap open android"
echo "2. Run on device/emulator: npx cap run android"
echo "3. Or build web version: npm run dev"
echo "3. Or test web version: npm run dev"
echo ""
echo "🔧 Troubleshooting:"
echo "- If Android Studio doesn't open, install it from https://developer.android.com/studio"
echo "- If sync fails, check Android SDK installation"
echo "- For web testing, run: npm run dev"

37
test-apps/setup-electron.sh

@ -3,19 +3,54 @@
# Electron Test App Setup Script
echo "🚀 Setting up Electron Test App..."
# Check if we're in the right directory
if [ ! -d "electron-test" ]; then
echo "❌ Error: electron-test directory not found!"
echo "Please run this script from the test-apps directory"
exit 1
fi
cd electron-test
# Check Node.js version
echo "🔍 Checking Node.js version..."
node_version=$(node --version 2>/dev/null)
if [ $? -ne 0 ]; then
echo "❌ Error: Node.js not found!"
echo "Please install Node.js 18+ from https://nodejs.org/"
exit 1
fi
echo "✅ Node.js version: $node_version"
# Install dependencies
echo "📦 Installing dependencies..."
npm install
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to install dependencies!"
exit 1
fi
# Build web assets
echo "🔨 Building web assets..."
npm run build-web
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to build web assets!"
exit 1
fi
echo ""
echo "✅ Electron test app setup complete!"
echo ""
echo "Next steps:"
echo "📋 Prerequisites check:"
echo "- Node.js installed: ✅"
echo "- Electron dependencies: ✅"
echo ""
echo "🚀 Next steps:"
echo "1. Run Electron app: npm start"
echo "2. Run in dev mode: npm run dev"
echo "3. Build and run: npm run electron"
echo ""
echo "🔧 Troubleshooting:"
echo "- If Electron doesn't start, check Node.js version (18+)"
echo "- For development, use: npm run dev"
echo "- Check console logs for detailed error information"

70
test-apps/setup-ios.sh

@ -3,37 +3,99 @@
# iOS Test App Setup Script
echo "🚀 Setting up iOS Test App..."
# Check if we're in the right directory
if [ ! -d "ios-test" ]; then
echo "❌ Error: ios-test directory not found!"
echo "Please run this script from the test-apps directory"
exit 1
fi
cd ios-test
# Check Node.js version
echo "🔍 Checking Node.js version..."
node_version=$(node --version 2>/dev/null)
if [ $? -ne 0 ]; then
echo "❌ Error: Node.js not found!"
echo "Please install Node.js 18+ from https://nodejs.org/"
exit 1
fi
echo "✅ Node.js version: $node_version"
# Install dependencies
echo "📦 Installing dependencies..."
npm install
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to install dependencies!"
exit 1
fi
# Install Capacitor CLI globally if not present
if ! command -v cap &> /dev/null; then
echo "🔧 Installing Capacitor CLI globally..."
npm install -g @capacitor/cli
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to install Capacitor CLI!"
exit 1
fi
else
echo "✅ Capacitor CLI already installed"
fi
# Initialize Capacitor
# Initialize Capacitor (only if not already initialized)
if [ ! -f "capacitor.config.ts" ]; then
echo "⚡ Initializing Capacitor..."
npx cap init "Daily Notification iOS Test" "com.timesafari.dailynotification.iostest"
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to initialize Capacitor!"
exit 1
fi
else
echo "✅ Capacitor already initialized"
fi
# Add iOS platform
# Add iOS platform (only if not already added)
if [ ! -d "ios" ]; then
echo "🍎 Adding iOS platform..."
npx cap add ios
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to add iOS platform!"
echo "Make sure Xcode and iOS SDK are installed"
exit 1
fi
else
echo "✅ iOS platform already added"
fi
# Build web assets
echo "🔨 Building web assets..."
npm run build
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to build web assets!"
exit 1
fi
# Sync to native
echo "🔄 Syncing to native..."
npx cap sync ios
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to sync to native!"
exit 1
fi
echo ""
echo "✅ iOS test app setup complete!"
echo ""
echo "Next steps:"
echo "📋 Prerequisites check:"
echo "- Xcode installed: $(command -v xcodebuild &> /dev/null && echo '✅' || echo '❌')"
echo "- iOS Simulator available: $(xcrun simctl list devices &> /dev/null && echo '✅' || echo '❌')"
echo ""
echo "🚀 Next steps:"
echo "1. Open Xcode: npx cap open ios"
echo "2. Run on device/simulator: npx cap run ios"
echo "3. Or build web version: npm run dev"
echo "3. Or test web version: npm run dev"
echo ""
echo "🔧 Troubleshooting:"
echo "- If Xcode doesn't open, install it from the Mac App Store"
echo "- If sync fails, check Xcode command line tools: xcode-select --install"
echo "- For web testing, run: npm run dev"

152
test-apps/test-api/.gitignore

@ -0,0 +1,152 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage
.grunt
# Bower dependency directory
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons
build/Release
# Dependency directories
jspm_packages/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.local
.env.production
# parcel-bundler cache
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage
.grunt
# Bower dependency directory
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

282
test-apps/test-api/README.md

@ -0,0 +1,282 @@
# Test API Server
A mock REST API server for testing the Daily Notification Plugin's network functionality, ETag support, and error handling capabilities.
## Features
- **Content Endpoints**: Generate mock notification content for different time slots
- **ETag Support**: Full HTTP caching with conditional requests (304 Not Modified)
- **Error Simulation**: Test various error scenarios (timeout, server error, rate limiting)
- **Metrics**: Monitor API usage and performance
- **CORS Enabled**: Cross-origin requests supported for web testing
## Quick Start
```bash
# Install dependencies
npm install
# Start server
npm start
# Development mode with auto-restart
npm run dev
```
## API Endpoints
### Health Check
```http
GET /health
```
**Response:**
```json
{
"status": "healthy",
"timestamp": 1703123456789,
"version": "1.0.0",
"endpoints": {
"content": "/api/content/:slotId",
"health": "/health",
"metrics": "/api/metrics",
"error": "/api/error/:type"
}
}
```
### Get Notification Content
```http
GET /api/content/:slotId
```
**Parameters:**
- `slotId`: Slot identifier in format `slot-HH:MM` (e.g., `slot-08:00`)
**Headers:**
- `If-None-Match`: ETag for conditional requests
**Response (200 OK):**
```json
{
"id": "abc12345",
"slotId": "slot-08:00",
"title": "Daily Update - 08:00",
"body": "Your personalized content for 08:00. Content ID: abc12345",
"timestamp": 1703123456789,
"priority": "high",
"category": "daily",
"actions": [
{ "id": "view", "title": "View Details" },
{ "id": "dismiss", "title": "Dismiss" }
],
"metadata": {
"source": "test-api",
"version": "1.0.0",
"generated": "2023-12-21T08:00:00.000Z"
}
}
```
**Response (304 Not Modified):**
When `If-None-Match` header matches current ETag.
### Update Content
```http
PUT /api/content/:slotId
```
**Body:**
```json
{
"content": {
"title": "Custom Title",
"body": "Custom body content"
}
}
```
### Clear All Content
```http
DELETE /api/content
```
### Simulate Errors
```http
GET /api/error/:type
```
**Error Types:**
- `timeout` - Simulates request timeout (15 seconds)
- `server-error` - Returns 500 Internal Server Error
- `not-found` - Returns 404 Not Found
- `rate-limit` - Returns 429 Rate Limit Exceeded
- `unauthorized` - Returns 401 Unauthorized
### API Metrics
```http
GET /api/metrics
```
**Response:**
```json
{
"timestamp": 1703123456789,
"contentStore": {
"size": 5,
"slots": ["slot-08:00", "slot-12:00", "slot-18:00"]
},
"etagStore": {
"size": 5,
"etags": [["slot-08:00", "\"abc123\""]]
},
"uptime": 3600,
"memory": {
"rss": 50331648,
"heapTotal": 20971520,
"heapUsed": 15728640,
"external": 1048576
}
}
```
## Usage Examples
### Basic Content Fetch
```bash
curl http://localhost:3001/api/content/slot-08:00
```
### ETag Conditional Request
```bash
# First request
curl -v http://localhost:3001/api/content/slot-08:00
# Second request with ETag (should return 304)
curl -v -H "If-None-Match: \"abc123\"" http://localhost:3001/api/content/slot-08:00
```
### Error Testing
```bash
# Test timeout
curl http://localhost:3001/api/error/timeout
# Test server error
curl http://localhost:3001/api/error/server-error
# Test rate limiting
curl http://localhost:3001/api/error/rate-limit
```
## Integration with Test Apps
### Android Test App
```typescript
// In your Android test app
const API_BASE_URL = 'http://10.0.2.2:3001'; // Android emulator localhost
const fetchContent = async (slotId: string) => {
const response = await fetch(`${API_BASE_URL}/api/content/${slotId}`);
return response.json();
};
```
### iOS Test App
```typescript
// In your iOS test app
const API_BASE_URL = 'http://localhost:3001'; // iOS simulator localhost
const fetchContent = async (slotId: string) => {
const response = await fetch(`${API_BASE_URL}/api/content/${slotId}`);
return response.json();
};
```
### Electron Test App
```typescript
// In your Electron test app
const API_BASE_URL = 'http://localhost:3001';
const fetchContent = async (slotId: string) => {
const response = await fetch(`${API_BASE_URL}/api/content/${slotId}`);
return response.json();
};
```
## Configuration
### Environment Variables
- `PORT`: Server port (default: 3001)
- `NODE_ENV`: Environment mode (development/production)
### CORS Configuration
The server is configured to allow cross-origin requests from any origin for testing purposes.
## Testing Scenarios
### 1. Basic Content Fetching
- Test successful content retrieval
- Verify content structure and format
- Check timestamp accuracy
### 2. ETag Caching
- Test conditional requests with `If-None-Match`
- Verify 304 Not Modified responses
- Test cache invalidation
### 3. Error Handling
- Test timeout scenarios
- Test server error responses
- Test rate limiting behavior
- Test network failure simulation
### 4. Performance Testing
- Test concurrent requests
- Monitor memory usage
- Test long-running scenarios
## Development
### Running in Development Mode
```bash
npm run dev
```
This uses `nodemon` for automatic server restart on file changes.
### Adding New Endpoints
1. Add route handler in `server.js`
2. Update health check endpoint list
3. Add documentation to this README
4. Add test cases if applicable
### Testing
```bash
npm test
```
## Troubleshooting
### Common Issues
1. **Port Already in Use**
```bash
# Kill process using port 3001
lsof -ti:3001 | xargs kill -9
```
2. **CORS Issues**
- Server is configured to allow all origins
- Check browser console for CORS errors
3. **Network Connectivity**
- Android emulator: Use `10.0.2.2` instead of `localhost`
- iOS simulator: Use `localhost` or `127.0.0.1`
- Physical devices: Use your computer's IP address
### Logs
The server logs all requests with timestamps and response codes for debugging.
## License
MIT License - See LICENSE file for details.

76
test-apps/test-api/SETUP.md

@ -0,0 +1,76 @@
# Test API Server Setup
## Overview
The Test API Server provides mock endpoints for testing the Daily Notification Plugin's network functionality, including ETag support, error handling, and content fetching.
## Quick Setup
```bash
# Navigate to test-api directory
cd test-apps/test-api
# Install dependencies
npm install
# Start server
npm start
```
## Integration with Test Apps
### Update Test App Configuration
Add the API base URL to your test app configuration:
```typescript
// In your test app's config
const API_CONFIG = {
baseUrl: 'http://localhost:3001', // Adjust for platform
endpoints: {
content: '/api/content',
health: '/health',
error: '/api/error',
metrics: '/api/metrics'
}
};
```
### Platform-Specific URLs
- **Web/Electron**: `http://localhost:3001`
- **Android Emulator**: `http://10.0.2.2:3001`
- **iOS Simulator**: `http://localhost:3001`
- **Physical Devices**: `http://[YOUR_IP]:3001`
## Testing Workflow
1. **Start API Server**: `npm start` in `test-apps/test-api/`
2. **Start Test App**: Run your platform-specific test app
3. **Test Scenarios**: Use the test app to validate plugin functionality
4. **Monitor API**: Check `/api/metrics` for usage statistics
## Available Test Scenarios
### Content Fetching
- Basic content retrieval
- ETag conditional requests
- Content updates and caching
### Error Handling
- Network timeouts
- Server errors
- Rate limiting
- Authentication failures
### Performance Testing
- Concurrent requests
- Memory usage monitoring
- Long-running scenarios
## Next Steps
1. Start the API server
2. Configure your test apps to use the API
3. Run through the test scenarios
4. Validate plugin functionality across platforms

305
test-apps/test-api/client.ts

@ -0,0 +1,305 @@
/**
* Test API Client for Daily Notification Plugin
*
* Demonstrates how to integrate with the test API server
* for validating plugin functionality.
*
* @author Matthew Raymer
* @version 1.0.0
*/
export interface TestAPIConfig {
baseUrl: string;
timeout: number;
}
export interface NotificationContent {
id: string;
slotId: string;
title: string;
body: string;
timestamp: number;
priority: string;
category: string;
actions: Array<{ id: string; title: string }>;
metadata: {
source: string;
version: string;
generated: string;
};
}
export interface APIResponse<T> {
data?: T;
error?: string;
status: number;
etag?: string;
fromCache: boolean;
}
export class TestAPIClient {
private config: TestAPIConfig;
private etagCache = new Map<string, string>();
constructor(config: TestAPIConfig) {
this.config = config;
}
/**
* Fetch notification content for a specific slot
* @param slotId - Slot identifier (e.g., 'slot-08:00')
* @returns Promise<APIResponse<NotificationContent>>
*/
async fetchContent(slotId: string): Promise<APIResponse<NotificationContent>> {
const url = `${this.config.baseUrl}/api/content/${slotId}`;
const headers: Record<string, string> = {};
// Add ETag for conditional request if we have cached content
const cachedETag = this.etagCache.get(slotId);
if (cachedETag) {
headers['If-None-Match'] = cachedETag;
}
try {
const response = await fetch(url, {
method: 'GET',
headers,
signal: AbortSignal.timeout(this.config.timeout)
});
const etag = response.headers.get('ETag');
const fromCache = response.status === 304;
if (fromCache) {
return {
status: response.status,
fromCache: true,
etag: cachedETag
};
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Cache ETag for future conditional requests
if (etag) {
this.etagCache.set(slotId, etag);
}
return {
data,
status: response.status,
etag,
fromCache: false
};
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
status: 0,
fromCache: false
};
}
}
/**
* Test error scenarios
* @param errorType - Type of error to simulate
* @returns Promise<APIResponse<any>>
*/
async testError(errorType: string): Promise<APIResponse<any>> {
const url = `${this.config.baseUrl}/api/error/${errorType}`;
try {
const response = await fetch(url, {
method: 'GET',
signal: AbortSignal.timeout(this.config.timeout)
});
const data = await response.json();
return {
data,
status: response.status,
fromCache: false
};
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
status: 0,
fromCache: false
};
}
}
/**
* Get API health status
* @returns Promise<APIResponse<any>>
*/
async getHealth(): Promise<APIResponse<any>> {
const url = `${this.config.baseUrl}/health`;
try {
const response = await fetch(url, {
method: 'GET',
signal: AbortSignal.timeout(this.config.timeout)
});
const data = await response.json();
return {
data,
status: response.status,
fromCache: false
};
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
status: 0,
fromCache: false
};
}
}
/**
* Get API metrics
* @returns Promise<APIResponse<any>>
*/
async getMetrics(): Promise<APIResponse<any>> {
const url = `${this.config.baseUrl}/api/metrics`;
try {
const response = await fetch(url, {
method: 'GET',
signal: AbortSignal.timeout(this.config.timeout)
});
const data = await response.json();
return {
data,
status: response.status,
fromCache: false
};
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
status: 0,
fromCache: false
};
}
}
/**
* Clear ETag cache
*/
clearCache(): void {
this.etagCache.clear();
}
/**
* Get cached ETags
* @returns Map of slotId to ETag
*/
getCachedETags(): Map<string, string> {
return new Map(this.etagCache);
}
}
/**
* Platform-specific API configuration
*/
export const getAPIConfig = (): TestAPIConfig => {
// Detect platform and set appropriate base URL
if (typeof window !== 'undefined') {
// Web/Electron
return {
baseUrl: 'http://localhost:3001',
timeout: 12000 // 12 seconds
};
}
// Default configuration
return {
baseUrl: 'http://localhost:3001',
timeout: 12000
};
};
/**
* Usage examples for test apps
*/
export const TestAPIExamples = {
/**
* Basic content fetching example
*/
async basicFetch() {
const client = new TestAPIClient(getAPIConfig());
console.log('Testing basic content fetch...');
const result = await client.fetchContent('slot-08:00');
if (result.error) {
console.error('Error:', result.error);
} else {
console.log('Success:', result.data);
console.log('ETag:', result.etag);
console.log('From cache:', result.fromCache);
}
},
/**
* ETag caching example
*/
async etagCaching() {
const client = new TestAPIClient(getAPIConfig());
console.log('Testing ETag caching...');
// First request
const result1 = await client.fetchContent('slot-08:00');
console.log('First request:', result1.fromCache ? 'From cache' : 'Fresh content');
// Second request (should be from cache)
const result2 = await client.fetchContent('slot-08:00');
console.log('Second request:', result2.fromCache ? 'From cache' : 'Fresh content');
},
/**
* Error handling example
*/
async errorHandling() {
const client = new TestAPIClient(getAPIConfig());
console.log('Testing error handling...');
const errorTypes = ['timeout', 'server-error', 'not-found', 'rate-limit'];
for (const errorType of errorTypes) {
const result = await client.testError(errorType);
console.log(`${errorType}:`, result.status, result.error || 'Success');
}
},
/**
* Health check example
*/
async healthCheck() {
const client = new TestAPIClient(getAPIConfig());
console.log('Testing health check...');
const result = await client.getHealth();
if (result.error) {
console.error('Health check failed:', result.error);
} else {
console.log('API is healthy:', result.data);
}
}
};

4799
test-apps/test-api/package-lock.json

File diff suppressed because it is too large

33
test-apps/test-api/package.json

@ -0,0 +1,33 @@
{
"name": "daily-notification-test-api",
"version": "1.0.0",
"description": "Test API server for Daily Notification Plugin validation",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest",
"demo": "node test-demo.js"
},
"keywords": [
"test",
"api",
"notification",
"capacitor",
"plugin"
],
"author": "Matthew Raymer",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5"
},
"devDependencies": {
"nodemon": "^3.0.1",
"jest": "^29.7.0",
"node-fetch": "^2.7.0"
},
"engines": {
"node": ">=18.0.0"
}
}

321
test-apps/test-api/server.js

@ -0,0 +1,321 @@
#!/usr/bin/env node
/**
* Test API Server for Daily Notification Plugin
*
* Provides mock content endpoints for testing the plugin's
* network fetching, ETag support, and error handling capabilities.
*
* @author Matthew Raymer
* @version 1.0.0
*/
const express = require('express');
const cors = require('cors');
const crypto = require('crypto');
const app = express();
const PORT = process.env.PORT || 3001;
// Middleware
app.use(cors());
app.use(express.json());
// In-memory storage for testing
let contentStore = new Map();
let etagStore = new Map();
/**
* Generate mock notification content for a given slot
* @param {string} slotId - The notification slot identifier
* @param {number} timestamp - Current timestamp
* @returns {Object} Mock notification content
*/
function generateMockContent(slotId, timestamp) {
const slotTime = slotId.split('-')[1] || '08:00';
const contentId = crypto.randomUUID().substring(0, 8);
return {
id: contentId,
slotId: slotId,
title: `Daily Update - ${slotTime}`,
body: `Your personalized content for ${slotTime}. Content ID: ${contentId}`,
timestamp: timestamp,
priority: 'high',
category: 'daily',
actions: [
{ id: 'view', title: 'View Details' },
{ id: 'dismiss', title: 'Dismiss' }
],
metadata: {
source: 'test-api',
version: '1.0.0',
generated: new Date(timestamp).toISOString()
}
};
}
/**
* Generate ETag for content
* @param {Object} content - Content object
* @returns {string} ETag value
*/
function generateETag(content) {
const contentString = JSON.stringify(content);
return `"${crypto.createHash('md5').update(contentString).digest('hex')}"`;
}
/**
* Store content with ETag
* @param {string} slotId - Slot identifier
* @param {Object} content - Content object
* @param {string} etag - ETag value
*/
function storeContent(slotId, content, etag) {
contentStore.set(slotId, content);
etagStore.set(slotId, etag);
}
/**
* Get stored content and ETag
* @param {string} slotId - Slot identifier
* @returns {Object} { content, etag } or null
*/
function getStoredContent(slotId) {
const content = contentStore.get(slotId);
const etag = etagStore.get(slotId);
return content && etag ? { content, etag } : null;
}
// Routes
/**
* Health check endpoint
*/
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: Date.now(),
version: '1.0.0',
endpoints: {
content: '/api/content/:slotId',
health: '/health',
metrics: '/api/metrics',
error: '/api/error/:type'
}
});
});
/**
* Get notification content for a specific slot
* Supports ETag conditional requests
*/
app.get('/api/content/:slotId', (req, res) => {
const { slotId } = req.params;
const ifNoneMatch = req.headers['if-none-match'];
const timestamp = Date.now();
console.log(`[${new Date().toISOString()}] GET /api/content/${slotId}`);
console.log(` If-None-Match: ${ifNoneMatch || 'none'}`);
// Validate slotId format
if (!slotId || !slotId.match(/^slot-\d{2}:\d{2}$/)) {
return res.status(400).json({
error: 'Invalid slotId format. Expected: slot-HH:MM',
provided: slotId
});
}
// Check if we have stored content
const stored = getStoredContent(slotId);
if (stored && ifNoneMatch === stored.etag) {
// Content hasn't changed, return 304 Not Modified
console.log(` → 304 Not Modified (ETag match)`);
return res.status(304).end();
}
// Generate new content
const content = generateMockContent(slotId, timestamp);
const etag = generateETag(content);
// Store for future ETag checks
storeContent(slotId, content, etag);
// Set ETag header
res.set('ETag', etag);
res.set('Cache-Control', 'no-cache');
res.set('Last-Modified', new Date(timestamp).toUTCString());
console.log(` → 200 OK (new content, ETag: ${etag})`);
res.json(content);
});
/**
* Simulate network errors for testing error handling
*/
app.get('/api/error/:type', (req, res) => {
const { type } = req.params;
console.log(`[${new Date().toISOString()}] GET /api/error/${type}`);
switch (type) {
case 'timeout':
// Simulate timeout by not responding
setTimeout(() => {
res.status(408).json({ error: 'Request timeout' });
}, 15000); // 15 second timeout
break;
case 'server-error':
res.status(500).json({
error: 'Internal server error',
code: 'INTERNAL_ERROR',
timestamp: Date.now()
});
break;
case 'not-found':
res.status(404).json({
error: 'Content not found',
code: 'NOT_FOUND',
slotId: req.query.slotId || 'unknown'
});
break;
case 'rate-limit':
res.status(429).json({
error: 'Rate limit exceeded',
code: 'RATE_LIMIT',
retryAfter: 60
});
break;
case 'unauthorized':
res.status(401).json({
error: 'Unauthorized',
code: 'UNAUTHORIZED'
});
break;
default:
res.status(400).json({
error: 'Unknown error type',
available: ['timeout', 'server-error', 'not-found', 'rate-limit', 'unauthorized']
});
}
});
/**
* API metrics endpoint
*/
app.get('/api/metrics', (req, res) => {
const metrics = {
timestamp: Date.now(),
contentStore: {
size: contentStore.size,
slots: Array.from(contentStore.keys())
},
etagStore: {
size: etagStore.size,
etags: Array.from(etagStore.entries())
},
uptime: process.uptime(),
memory: process.memoryUsage()
};
res.json(metrics);
});
/**
* Clear stored content (for testing)
*/
app.delete('/api/content', (req, res) => {
contentStore.clear();
etagStore.clear();
res.json({
message: 'All stored content cleared',
timestamp: Date.now()
});
});
/**
* Update content for a specific slot (for testing content changes)
*/
app.put('/api/content/:slotId', (req, res) => {
const { slotId } = req.params;
const { content } = req.body;
if (!content) {
return res.status(400).json({
error: 'Content is required'
});
}
const timestamp = Date.now();
const etag = generateETag(content);
storeContent(slotId, content, etag);
res.set('ETag', etag);
res.json({
message: 'Content updated',
slotId,
etag,
timestamp
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(`[${new Date().toISOString()}] Error:`, err);
res.status(500).json({
error: 'Internal server error',
message: err.message,
timestamp: Date.now()
});
});
// 404 handler
app.use((req, res) => {
res.status(404).json({
error: 'Endpoint not found',
path: req.path,
method: req.method,
timestamp: Date.now()
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 Test API Server running on port ${PORT}`);
console.log(`📋 Available endpoints:`);
console.log(` GET /health - Health check`);
console.log(` GET /api/content/:slotId - Get notification content`);
console.log(` PUT /api/content/:slotId - Update content`);
console.log(` DELETE /api/content - Clear all content`);
console.log(` GET /api/error/:type - Simulate errors`);
console.log(` GET /api/metrics - API metrics`);
console.log(``);
console.log(`🔧 Environment:`);
console.log(` NODE_ENV: ${process.env.NODE_ENV || 'development'}`);
console.log(` PORT: ${PORT}`);
console.log(``);
console.log(`📝 Usage examples:`);
console.log(` curl http://localhost:${PORT}/health`);
console.log(` curl http://localhost:${PORT}/api/content/slot-08:00`);
console.log(` curl -H "If-None-Match: \\"abc123\\"" http://localhost:${PORT}/api/content/slot-08:00`);
console.log(` curl http://localhost:${PORT}/api/error/timeout`);
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\n🛑 Shutting down Test API Server...');
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('\n🛑 Shutting down Test API Server...');
process.exit(0);
});

294
test-apps/test-api/test-demo.js

@ -0,0 +1,294 @@
#!/usr/bin/env node
/**
* Test API Demo Script
*
* Demonstrates the Test API Server functionality
* and validates all endpoints work correctly.
*
* @author Matthew Raymer
* @version 1.0.0
*/
const fetch = require('node-fetch');
const API_BASE_URL = 'http://localhost:3001';
/**
* Make HTTP request with timeout
* @param {string} url - Request URL
* @param {Object} options - Fetch options
* @returns {Promise<Object>} Response data
*/
async function makeRequest(url, options = {}) {
try {
const response = await fetch(url, {
timeout: 10000,
...options
});
const data = await response.json();
return {
status: response.status,
data,
headers: Object.fromEntries(response.headers.entries())
};
} catch (error) {
return {
status: 0,
error: error.message
};
}
}
/**
* Test health endpoint
*/
async function testHealth() {
console.log('🔍 Testing health endpoint...');
const result = await makeRequest(`${API_BASE_URL}/health`);
if (result.error) {
console.error('❌ Health check failed:', result.error);
return false;
}
console.log('✅ Health check passed');
console.log(' Status:', result.status);
console.log(' Version:', result.data.version);
console.log(' Endpoints:', Object.keys(result.data.endpoints).length);
return true;
}
/**
* Test content fetching
*/
async function testContentFetching() {
console.log('\n📱 Testing content fetching...');
const slotId = 'slot-08:00';
const result = await makeRequest(`${API_BASE_URL}/api/content/${slotId}`);
if (result.error) {
console.error('❌ Content fetch failed:', result.error);
return false;
}
console.log('✅ Content fetch passed');
console.log(' Status:', result.status);
console.log(' Slot ID:', result.data.slotId);
console.log(' Title:', result.data.title);
console.log(' ETag:', result.headers.etag);
return result.headers.etag;
}
/**
* Test ETag caching
*/
async function testETagCaching(etag) {
console.log('\n🔄 Testing ETag caching...');
const slotId = 'slot-08:00';
const result = await makeRequest(`${API_BASE_URL}/api/content/${slotId}`, {
headers: {
'If-None-Match': etag
}
});
if (result.error) {
console.error('❌ ETag test failed:', result.error);
return false;
}
if (result.status === 304) {
console.log('✅ ETag caching works (304 Not Modified)');
return true;
} else {
console.log('⚠️ ETag caching unexpected response:', result.status);
return false;
}
}
/**
* Test error scenarios
*/
async function testErrorScenarios() {
console.log('\n🚨 Testing error scenarios...');
const errorTypes = ['server-error', 'not-found', 'rate-limit', 'unauthorized'];
let passed = 0;
for (const errorType of errorTypes) {
const result = await makeRequest(`${API_BASE_URL}/api/error/${errorType}`);
if (result.error) {
console.log(`${errorType}: ${result.error}`);
} else {
console.log(`${errorType}: ${result.status}`);
passed++;
}
}
console.log(` Passed: ${passed}/${errorTypes.length}`);
return passed === errorTypes.length;
}
/**
* Test metrics endpoint
*/
async function testMetrics() {
console.log('\n📊 Testing metrics endpoint...');
const result = await makeRequest(`${API_BASE_URL}/api/metrics`);
if (result.error) {
console.error('❌ Metrics test failed:', result.error);
return false;
}
console.log('✅ Metrics endpoint works');
console.log(' Content store size:', result.data.contentStore.size);
console.log(' ETag store size:', result.data.etagStore.size);
console.log(' Uptime:', Math.round(result.data.uptime), 'seconds');
return true;
}
/**
* Test content update
*/
async function testContentUpdate() {
console.log('\n✏️ Testing content update...');
const slotId = 'slot-08:00';
const newContent = {
content: {
title: 'Updated Test Title',
body: 'This is updated test content',
timestamp: Date.now()
}
};
const result = await makeRequest(`${API_BASE_URL}/api/content/${slotId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newContent)
});
if (result.error) {
console.error('❌ Content update failed:', result.error);
return false;
}
console.log('✅ Content update works');
console.log(' Status:', result.status);
console.log(' New ETag:', result.data.etag);
return true;
}
/**
* Test content clearing
*/
async function testContentClearing() {
console.log('\n🗑️ Testing content clearing...');
const result = await makeRequest(`${API_BASE_URL}/api/content`, {
method: 'DELETE'
});
if (result.error) {
console.error('❌ Content clearing failed:', result.error);
return false;
}
console.log('✅ Content clearing works');
console.log(' Status:', result.status);
return true;
}
/**
* Main test runner
*/
async function runTests() {
console.log('🚀 Starting Test API validation...\n');
const tests = [
{ name: 'Health Check', fn: testHealth },
{ name: 'Content Fetching', fn: testContentFetching },
{ name: 'ETag Caching', fn: testETagCaching },
{ name: 'Error Scenarios', fn: testErrorScenarios },
{ name: 'Metrics', fn: testMetrics },
{ name: 'Content Update', fn: testContentUpdate },
{ name: 'Content Clearing', fn: testContentClearing }
];
let passed = 0;
let etag = null;
for (const test of tests) {
try {
if (test.name === 'ETag Caching' && etag) {
const result = await test.fn(etag);
if (result) passed++;
} else {
const result = await test.fn();
if (result) {
passed++;
if (test.name === 'Content Fetching' && typeof result === 'string') {
etag = result;
}
}
}
} catch (error) {
console.error(`${test.name} failed with error:`, error.message);
}
}
console.log(`\n📋 Test Results: ${passed}/${tests.length} passed`);
if (passed === tests.length) {
console.log('🎉 All tests passed! Test API is working correctly.');
} else {
console.log('⚠️ Some tests failed. Check the output above for details.');
}
console.log('\n💡 Next steps:');
console.log(' 1. Start your test app');
console.log(' 2. Configure it to use this API');
console.log(' 3. Test plugin functionality');
console.log(' 4. Monitor API metrics at /api/metrics');
}
// Check if API server is running
async function checkServer() {
try {
const result = await makeRequest(`${API_BASE_URL}/health`);
if (result.error) {
console.error('❌ Cannot connect to Test API Server');
console.error(' Make sure the server is running: npm start');
console.error(' Server should be available at:', API_BASE_URL);
process.exit(1);
}
} catch (error) {
console.error('❌ Cannot connect to Test API Server');
console.error(' Make sure the server is running: npm start');
console.error(' Server should be available at:', API_BASE_URL);
process.exit(1);
}
}
// Run tests
checkServer().then(() => {
runTests().catch(error => {
console.error('❌ Test runner failed:', error.message);
process.exit(1);
});
});
Loading…
Cancel
Save