Browse Source
- 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
16 changed files with 12331 additions and 48 deletions
@ -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 |
@ -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 |
File diff suppressed because it is too large
@ -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" |
@ -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 |
@ -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. |
@ -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 |
@ -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); |
||||
|
} |
||||
|
} |
||||
|
}; |
File diff suppressed because it is too large
@ -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" |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
}); |
@ -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…
Reference in new issue