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)
This commit is contained in:
@@ -11,9 +11,12 @@ test-apps/
|
|||||||
├── android-test/ # Android test app
|
├── android-test/ # Android test app
|
||||||
├── ios-test/ # iOS test app
|
├── ios-test/ # iOS test app
|
||||||
├── electron-test/ # Electron test app
|
├── electron-test/ # Electron test app
|
||||||
|
├── test-api/ # Test API server
|
||||||
├── setup-android.sh # Android setup script
|
├── setup-android.sh # Android setup script
|
||||||
├── setup-ios.sh # iOS setup script
|
├── setup-ios.sh # iOS setup script
|
||||||
├── setup-electron.sh # Electron setup script
|
├── setup-electron.sh # Electron setup script
|
||||||
|
├── check-environment.sh # Environment verification
|
||||||
|
├── SETUP_GUIDE.md # Enhanced setup guide
|
||||||
└── README.md # This guide
|
└── README.md # This guide
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -29,36 +32,32 @@ test-apps/
|
|||||||
|
|
||||||
### Option 1: Automated Setup (Recommended)
|
### Option 1: Automated Setup (Recommended)
|
||||||
```bash
|
```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-android.sh
|
||||||
./setup-ios.sh
|
./setup-ios.sh
|
||||||
./setup-electron.sh
|
./setup-electron.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**⚠️ Important**: Run setup scripts from the `test-apps` directory, not from individual platform directories.
|
||||||
|
|
||||||
### Option 2: Manual Setup
|
### Option 2: Manual Setup
|
||||||
|
See [Enhanced Setup Guide](SETUP_GUIDE.md) for detailed manual setup instructions and troubleshooting.
|
||||||
|
|
||||||
|
### Prerequisites Check
|
||||||
```bash
|
```bash
|
||||||
# Android
|
# Check your environment before setup
|
||||||
cd android-test
|
./check-environment.sh
|
||||||
npm install
|
|
||||||
npx cap init "Daily Notification Android Test" "com.timesafari.dailynotification.androidtest"
|
|
||||||
npx cap add android
|
|
||||||
npm run build
|
|
||||||
npx cap sync android
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Electron
|
|
||||||
cd electron-test
|
|
||||||
npm install
|
|
||||||
npm run build-web
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**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
|
## Test App Features
|
||||||
|
|
||||||
Each test app includes:
|
Each test app includes:
|
||||||
@@ -72,6 +71,42 @@ Each test app includes:
|
|||||||
- **Error Handling**: Comprehensive error testing
|
- **Error Handling**: Comprehensive error testing
|
||||||
- **Debug Information**: Platform-specific debug data
|
- **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
|
## Platform-Specific Testing
|
||||||
|
|
||||||
### Android Test App
|
### Android Test App
|
||||||
@@ -140,16 +175,28 @@ npm run dev # Run in development mode
|
|||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
1. **Build Failures**: Ensure all dependencies installed
|
1. **"Unknown command: cap"** → Install Capacitor CLI: `npm install -g @capacitor/cli`
|
||||||
2. **Platform Errors**: Check platform-specific SDKs installed
|
2. **"android platform has not been added yet"** → Run `npx cap add android` first
|
||||||
3. **Permission Issues**: Verify platform permissions configured
|
3. **Build failures** → Check Node.js version (18+) and clear cache: `npm cache clean --force`
|
||||||
4. **Sync Problems**: Run `npx cap sync` after changes
|
4. **Platform errors** → Verify platform-specific SDKs are installed
|
||||||
|
|
||||||
### Development Tips
|
### Quick Fixes
|
||||||
- Use `npm run dev` for web testing
|
```bash
|
||||||
- Use platform-specific tools for native testing
|
# Check environment
|
||||||
- Check console logs for detailed error information
|
./check-environment.sh
|
||||||
- Test on both physical devices and simulators
|
|
||||||
|
# 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
|
## Next Steps
|
||||||
|
|
||||||
|
|||||||
307
test-apps/SETUP_GUIDE.md
Normal file
307
test-apps/SETUP_GUIDE.md
Normal file
@@ -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
Normal file
160
test-apps/android-test/GRADLE_TROUBLESHOOTING.md
Normal file
@@ -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
generated
Normal file
5214
test-apps/android-test/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
112
test-apps/check-environment.sh
Executable file
112
test-apps/check-environment.sh
Executable file
@@ -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"
|
||||||
@@ -3,37 +3,121 @@
|
|||||||
# Android Test App Setup Script
|
# Android Test App Setup Script
|
||||||
echo "🚀 Setting up Android Test App..."
|
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
|
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
|
# Install dependencies
|
||||||
echo "📦 Installing dependencies..."
|
echo "📦 Installing dependencies..."
|
||||||
npm install
|
npm install
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Error: Failed to install dependencies!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Install Capacitor CLI globally if not present
|
# Install Capacitor CLI globally if not present
|
||||||
if ! command -v cap &> /dev/null; then
|
if ! command -v cap &> /dev/null; then
|
||||||
echo "🔧 Installing Capacitor CLI globally..."
|
echo "🔧 Installing Capacitor CLI globally..."
|
||||||
npm install -g @capacitor/cli
|
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
|
fi
|
||||||
|
|
||||||
# Initialize Capacitor
|
# Initialize Capacitor (only if not already initialized)
|
||||||
echo "⚡ Initializing Capacitor..."
|
if [ ! -f "capacitor.config.ts" ]; then
|
||||||
npx cap init "Daily Notification Android Test" "com.timesafari.dailynotification.androidtest"
|
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)
|
||||||
echo "📱 Adding Android platform..."
|
if [ ! -d "android" ]; then
|
||||||
npx cap add android
|
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
|
# Build web assets
|
||||||
echo "🔨 Building web assets..."
|
echo "🔨 Building web assets..."
|
||||||
npm run build
|
npm run build
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Error: Failed to build web assets!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Sync to native
|
# Sync to native
|
||||||
echo "🔄 Syncing to native..."
|
echo "🔄 Syncing to native..."
|
||||||
npx cap sync android
|
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 "✅ Android test app setup complete!"
|
||||||
echo ""
|
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 "1. Open Android Studio: npx cap open android"
|
||||||
echo "2. Run on device/emulator: npx cap run 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"
|
||||||
|
|||||||
@@ -3,19 +3,54 @@
|
|||||||
# Electron Test App Setup Script
|
# Electron Test App Setup Script
|
||||||
echo "🚀 Setting up Electron Test App..."
|
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
|
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
|
# Install dependencies
|
||||||
echo "📦 Installing dependencies..."
|
echo "📦 Installing dependencies..."
|
||||||
npm install
|
npm install
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Error: Failed to install dependencies!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Build web assets
|
# Build web assets
|
||||||
echo "🔨 Building web assets..."
|
echo "🔨 Building web assets..."
|
||||||
npm run build-web
|
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 "✅ Electron test app setup complete!"
|
||||||
echo ""
|
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 "1. Run Electron app: npm start"
|
||||||
echo "2. Run in dev mode: npm run dev"
|
echo "2. Run in dev mode: npm run dev"
|
||||||
echo "3. Build and run: npm run electron"
|
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"
|
||||||
|
|||||||
@@ -3,37 +3,99 @@
|
|||||||
# iOS Test App Setup Script
|
# iOS Test App Setup Script
|
||||||
echo "🚀 Setting up iOS Test App..."
|
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
|
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
|
# Install dependencies
|
||||||
echo "📦 Installing dependencies..."
|
echo "📦 Installing dependencies..."
|
||||||
npm install
|
npm install
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Error: Failed to install dependencies!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Install Capacitor CLI globally if not present
|
# Install Capacitor CLI globally if not present
|
||||||
if ! command -v cap &> /dev/null; then
|
if ! command -v cap &> /dev/null; then
|
||||||
echo "🔧 Installing Capacitor CLI globally..."
|
echo "🔧 Installing Capacitor CLI globally..."
|
||||||
npm install -g @capacitor/cli
|
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
|
fi
|
||||||
|
|
||||||
# Initialize Capacitor
|
# Initialize Capacitor (only if not already initialized)
|
||||||
echo "⚡ Initializing Capacitor..."
|
if [ ! -f "capacitor.config.ts" ]; then
|
||||||
npx cap init "Daily Notification iOS Test" "com.timesafari.dailynotification.iostest"
|
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)
|
||||||
echo "🍎 Adding iOS platform..."
|
if [ ! -d "ios" ]; then
|
||||||
npx cap add ios
|
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
|
# Build web assets
|
||||||
echo "🔨 Building web assets..."
|
echo "🔨 Building web assets..."
|
||||||
npm run build
|
npm run build
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Error: Failed to build web assets!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Sync to native
|
# Sync to native
|
||||||
echo "🔄 Syncing to native..."
|
echo "🔄 Syncing to native..."
|
||||||
npx cap sync ios
|
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 "✅ iOS test app setup complete!"
|
||||||
echo ""
|
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 "1. Open Xcode: npx cap open ios"
|
||||||
echo "2. Run on device/simulator: npx cap run 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
vendored
Normal file
152
test-apps/test-api/.gitignore
vendored
Normal file
@@ -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
Normal file
282
test-apps/test-api/README.md
Normal file
@@ -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
Normal file
76
test-apps/test-api/SETUP.md
Normal file
@@ -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
Normal file
305
test-apps/test-api/client.ts
Normal file
@@ -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
generated
Normal file
4799
test-apps/test-api/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
test-apps/test-api/package.json
Normal file
33
test-apps/test-api/package.json
Normal file
@@ -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
Normal file
321
test-apps/test-api/server.js
Normal file
@@ -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
Normal file
294
test-apps/test-api/test-demo.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user