Files
daily-notification-plugin/test-apps/ios-test-app/test-phase1.sh
Jose Olarte III d8a0eaf413 refactor(android,ios): rename package com.timesafari to org.timesafari.dailynotification
- Android: move plugin source to org/timesafari/dailynotification, update
  namespace, manifest package, and all package/imports; change intent actions
  to org.timesafari.daily.NOTIFICATION and DISMISS
- iOS: update bundle IDs, BGTask identifiers, subsystem labels, and queue
  names in Plugin and Xcode projects
- Capacitor: update plugin class registration and appIds in configs
- Test apps (android-test-app, daily-notification-test, ios-test-app):
  applicationId/bundleId, manifests, ProGuard, scripts, and docs
- Docs: bulk update references; add CONSUMING_APP_MIGRATION_COM_TO_ORG.md
  for consuming app migration

BREAKING CHANGE: Consuming apps must update plugin class to
org.timesafari.dailynotification.DailyNotificationPlugin, manifest
receivers/actions, and iOS BGTask identifiers per migration doc.
2026-03-12 14:26:07 +08:00

567 lines
21 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Phase 1 Testing Script - iOS Interactive Test Runner
# Guides through all Phase 1 tests with clear prompts for UI interaction
# Adapted from Android test-phase1.sh for iOS testing
set -e # Exit on error
# Source shared library (if exists)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "${SCRIPT_DIR}/ios-test-lib.sh" ]; then
source "${SCRIPT_DIR}/ios-test-lib.sh"
fi
# Phase 1 specific configuration
APP_BUNDLE_ID="org.timesafari.dailynotification.test"
SIMULATOR_DEVICE="iPhone 15"
LOG_PREFIX="DNP"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Helper functions
print_header() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
}
print_step() {
echo -e "${GREEN}[STEP $1]${NC} $2"
echo ""
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
wait_for_user() {
echo ""
read -p "Press Enter to continue..."
echo ""
}
wait_for_ui_action() {
echo ""
echo -e "${YELLOW}$1${NC}"
echo ""
read -p "Press Enter when done..."
echo ""
}
# iOS-specific helper functions
get_simulator_id() {
# First try to find a booted device matching the name
# Extract UUID from line like: " iPhone 15 (6514F1D6-80C2-4D0E-8CB4-6F561C8EA1F1) (Booted)"
local booted_id=$(xcrun simctl list devices | grep "${SIMULATOR_DEVICE}" | grep "Booted" | head -1 | sed -E 's/.*\(([A-F0-9-]{36})\).*\(Booted\).*/\1/')
if [ -n "${booted_id}" ] && [ "${booted_id}" != "Booted" ]; then
echo "${booted_id}"
return 0
fi
# If no booted device, try available devices
local available_id=$(xcrun simctl list devices available | grep "${SIMULATOR_DEVICE}" | head -1 | sed -E 's/.*\(([A-F0-9-]{36})\).*/\1/')
if [ -n "${available_id}" ]; then
echo "${available_id}"
return 0
fi
# Last resort: try any device with similar name (handles "iPhone 15" vs "iPhone 15 Pro")
# Prefer booted devices
local any_id=$(xcrun simctl list devices | grep -i "iphone.*15" | grep "Booted" | head -1 | sed -E 's/.*\(([A-F0-9-]{36})\).*\(Booted\).*/\1/')
if [ -n "${any_id}" ] && [ "${any_id}" != "Booted" ]; then
echo "${any_id}"
return 0
fi
# Try any iPhone 15 device (not necessarily booted)
any_id=$(xcrun simctl list devices | grep -i "iphone.*15" | head -1 | sed -E 's/.*\(([A-F0-9-]{36})\).*/\1/')
if [ -n "${any_id}" ]; then
echo "${any_id}"
return 0
fi
return 1
}
get_app_logs() {
local device_id=$1
local lines=${2:-50}
# Use log show (historical) instead of log stream (live) to avoid hanging
# This matches Android's approach of using logcat -d (historical logs)
# log stream can block indefinitely waiting for new logs
# Remove predicate to catch all logs (plugin logs may not match processImagePath predicate)
xcrun simctl spawn "${device_id}" log show --last 2m --style=compact 2>/dev/null | grep -iE "(dailynotification|ios-test-app|App)" | tail -n "${lines}" || echo ""
}
check_plugin_configured() {
print_info "Checking if plugin is configured..."
local device_id=$(get_simulator_id)
if [ -z "${device_id}" ]; then
print_error "Simulator not found: ${SIMULATOR_DEVICE}"
return 1
fi
# Check multiple ways to determine if app is installed/configured:
# 1. Check if app container exists (most reliable for installed apps)
local app_container=$(xcrun simctl get_app_container "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || echo "")
# 2. Check if app is listed in simulator (installed)
local app_listed=$(xcrun simctl listapps "${device_id}" 2>/dev/null | grep -c "${APP_BUNDLE_ID}" || echo "0")
app_listed=$(echo "${app_listed}" | tr -d '\n' | head -1)
app_listed=${app_listed:-0}
# 3. Check if app data directory exists (indicates app has been launched)
local data_root="$HOME/Library/Developer/CoreSimulator/Devices/${device_id}/data/Containers/Data/Application"
local app_data_exists=""
if [ -d "${data_root}" ]; then
# Check if any app data directory exists (app has been launched at least once)
app_data_exists=$(find "${data_root}" -maxdepth 1 -type d 2>/dev/null | head -1 || echo "")
fi
# If any check indicates app exists, consider it potentially configured
if [ -n "${app_container}" ] || [ "${app_listed}" -gt "0" ] || [ -n "${app_data_exists}" ]; then
print_success "App detected (plugin may be configured)"
print_info "Please verify in the app UI that you see:"
echo " ⚙️ Plugin Settings: ✅ Configured"
echo " 🔌 Native Fetcher: ✅ Configured"
echo ""
echo "If both show ✅, the plugin is configured and you can skip configuration."
echo "If not configured, you'll need to click 'Configure Plugin' in the app UI."
return 0
else
print_info "Plugin not configured (no app data found)"
print_info "You will need to click 'Configure Plugin' in the app UI"
return 1
fi
}
ensure_plugin_configured() {
if check_plugin_configured; then
# Plugin might be configured, but let user verify (matches Android pattern)
wait_for_ui_action "Please check the Plugin Status section at the top of the app.
If you see:
- ⚙️ Plugin Settings: ✅ Configured
- 🔌 Native Fetcher: ✅ Configured
- 🔔 Notifications: ✅ Granted (or similar)
Then the plugin is already configured - just press Enter to continue.
If any show ❌ or 'Not configured':
- Click 'Request Permissions' if notifications are not granted
- Click 'Configure Plugin' if settings/fetcher are not configured
- Wait for all to show ✅, then press Enter."
# Give a moment for any configuration that just happened
sleep 2
print_success "Continuing with tests (plugin configuration verified or skipped)"
return 0
else
# Plugin definitely needs configuration
print_info "Plugin needs configuration"
# First ensure permissions
ensure_permissions
wait_for_ui_action "Click the 'Configure Plugin' button in the app UI.
Wait for the status to update:
- ⚙️ Plugin Settings: Should change to ✅ Configured
- 🔌 Native Fetcher: Should change to ✅ Configured
Once both show ✅, press Enter to continue."
# Verify configuration completed
sleep 2
print_success "Plugin configuration completed (or verified)"
fi
}
check_permissions() {
print_info "Checking notification permissions..."
# Note: iOS permissions are checked at runtime, not via command line
# We can only check if the app has been granted permission by checking logs
print_info "iOS notification permissions are checked at runtime."
print_info "Please verify in the app UI that notifications are authorized."
print_info "If not authorized, you'll need to grant permission in the app."
return 0
}
ensure_permissions() {
if check_permissions; then
print_success "Permissions check passed"
return 0
else
print_info "Notification permissions needed"
wait_for_ui_action "In the app UI, click the 'Request Permissions' button.
This will show a system permission dialog.
Steps:
1. Click 'Request Permissions' button
2. In the system dialog, tap 'Allow' to grant notification permission
3. Return to the app and verify the status shows:
- 🔔 Notifications: ✅ Granted (or similar)
Once permission is granted, press Enter to continue."
return 0
fi
}
launch_app() {
print_info "Launching app on simulator..."
local device_id=$(get_simulator_id)
if [ -z "${device_id}" ]; then
print_error "Simulator not found: ${SIMULATOR_DEVICE}"
print_info "Available simulators:"
xcrun simctl list devices available | grep "iPhone" | head -5
return 1
fi
# Boot simulator if not running
local booted=$(xcrun simctl list devices | grep "${device_id}" | grep -c "Booted" 2>/dev/null || echo "0")
booted=$(echo "${booted}" | tr -d '\n' | head -1) # Remove newlines and take first value
booted=${booted:-0} # Default to 0 if empty
if [ "${booted}" -eq "0" ]; then
print_info "Booting simulator..."
xcrun simctl boot "${device_id}" 2>/dev/null || true
sleep 3
fi
# Launch app
xcrun simctl launch "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || {
print_warn "App may already be running or needs to be built first"
print_info "Please build and run the app in Xcode first:"
echo " 1. Open: test-apps/ios-test-app/ios/App/App.xcworkspace"
echo " 2. Select simulator: ${SIMULATOR_DEVICE}"
echo " 3. Press Cmd+R to build and run"
echo ""
wait_for_user
}
sleep 2 # Give app time to launch
print_success "App launched"
}
get_pending_notifications() {
local device_id=$(get_simulator_id)
if [ -z "${device_id}" ]; then
echo "0"
return
fi
# Note: iOS doesn't provide direct command-line access to pending notifications like Android's dumpsys alarm
# We check logs for the explicit pendingCount that the plugin now logs after scheduling
# Get logs directly without predicate to catch plugin logs (plugin runs in app process)
local logs=$(xcrun simctl spawn "${device_id}" log show --last 2m --style=compact 2>/dev/null | grep -iE "(DailyNotificationScheduler|pendingCount|dailynotification)" | tail -100 || echo "")
# Method 1: Look for explicit pendingCount in scheduling logs (most reliable)
# The plugin logs: "Notification scheduled successfully for ..., id=..., pendingCount=X"
# Match case-insensitive and extract the number after pendingCount=
local pending_count=$(echo "${logs}" | grep -iE "pendingCount[=:][[:space:]]*[0-9]+" | tail -1 | grep -oE "[0-9]+" | head -1 || echo "")
# Method 2: Look for "pending" count in status responses
# The plugin's getNotificationStatus returns "pending": count
local status_count=$(echo "${logs}" | grep -E "\"pending\"[[:space:]]*:[[:space:]]*[0-9]+" | tail -1 | grep -oE "[0-9]+" | head -1 || echo "")
# Method 3: Count unique notification IDs that were scheduled (fallback)
# Extract notification IDs from scheduling logs (format: "id=daily_...")
local notification_ids=$(echo "${logs}" | grep -oE "id=[a-zA-Z0-9_.-]+" | sed 's/id=//' | sort -u | wc -l | tr -d ' ')
notification_ids=${notification_ids:-0}
# Prefer explicit pendingCount from scheduling log (most reliable)
if [ -n "${pending_count}" ] && [ "${pending_count}" -ge "0" ] 2>/dev/null && [ "${pending_count}" -le "64" ] 2>/dev/null; then
echo "${pending_count}"
# Otherwise use status count if available
elif [ -n "${status_count}" ] && [ "${status_count}" -ge "0" ] 2>/dev/null && [ "${status_count}" -le "64" ] 2>/dev/null; then
echo "${status_count}"
# Fallback to counting unique notification IDs
elif [ "${notification_ids}" -gt "0" ]; then
echo "${notification_ids}"
# Default to 0 if nothing found
else
echo "0"
fi
}
should_run_test() {
local test_id=$1
shift
local selected_tests=("$@")
if [ ${#selected_tests[@]} -eq 0 ]; then
return 0 # Run all tests if none specified
fi
for selected in "${selected_tests[@]}"; do
if [ "${selected}" = "${test_id}" ]; then
return 0
fi
done
return 1
}
# ============================================
# TEST 0: Daily Rollover (Core Contract Verification)
# ============================================
if should_run_test "0" "$@"; then
print_header "TEST 0: Daily Rollover Verification"
echo "Purpose: Verify that after a notification fires, the next day's"
echo " schedule is correctly computed and only ONE notification exists."
echo ""
echo "Note: This test verifies the core 'one notification per day' contract."
echo " It requires either:"
echo " 1. Scheduling a notification for 'now + N seconds' and waiting, OR"
echo " 2. Manipulating the simulator clock to cross the fire boundary."
echo ""
wait_for_user
print_step "1" "Schedule a test notification for near-future..."
launch_app
ensure_plugin_configured
INITIAL_COUNT=$(get_pending_notifications)
print_info "Current pending notifications: ${INITIAL_COUNT}"
wait_for_ui_action "In the app UI, schedule a daily notification.
For this test, you may want to schedule it for a time very soon
(e.g., 1-2 minutes from now) to observe the rollover behavior.
This will schedule:
- 1 notification (UNUserNotificationCenter) for the specified time
- 1 prefetch task (BGTaskScheduler) for 2 minutes before that time"
sleep 3 # Give notification time to be registered
POST_SCHEDULE_COUNT=$(get_pending_notifications)
print_info "Pending notifications after scheduling: ${POST_SCHEDULE_COUNT}"
print_step "2" "Manual verification steps..."
echo ""
echo "To complete this test, you need to:"
echo " 1. Wait for the notification to fire (or advance simulator clock)"
echo " 2. Check that the plugin:"
echo " - Computed the next day's time (24 hours later)"
echo " - Scheduled exactly ONE notification for tomorrow"
echo " - Did NOT create duplicate notifications"
echo " 3. Verify in logs (Xcode Console or Console.app):"
echo " - Next run time calculation shows tomorrow's time"
echo " - Only one notification scheduled"
echo ""
echo "Expected log patterns:"
echo " DNP-SCHEDULE: Scheduling next daily notification: ... source=ROLLOVER_ON_FIRE"
echo " DNP-NOTIFY: Scheduling notification: triggerTime=<tomorrow's time>"
echo ""
wait_for_ui_action "After the notification fires (or you advance the clock),
check the logs and verify:
1. Only ONE notification exists (one per day)
2. The notification time is for tomorrow (24 hours later)
3. No duplicate notifications were created
Press Enter when verification is complete."
print_success "TEST 0: Daily Rollover Verification - Manual verification required"
fi
# ============================================
# TEST 1: Cold Start Recovery
# ============================================
if should_run_test "1" "$@"; then
print_header "TEST 1: Cold Start Recovery"
echo "Purpose: Verify that when the app launches after termination,"
echo " missed notifications are detected and future notifications"
echo " are verified/rescheduled."
echo ""
wait_for_user
print_step "1" "Schedule a notification for future time..."
launch_app
ensure_permissions
wait_for_ui_action "In the app UI, schedule a daily notification for a future time
(e.g., 1 hour from now).
This creates a notification that should persist across app termination."
sleep 2
print_step "2" "Terminate app (simulate cold start)..."
print_info "Terminating app to simulate cold start scenario..."
device_id=$(get_simulator_id)
xcrun simctl terminate "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || true
print_info "App terminated. Waiting 5 seconds..."
sleep 5
print_step "3" "Launch app and verify recovery..."
launch_app
print_info "Checking logs for recovery activity..."
sleep 3
device_id=$(get_simulator_id)
logs=$(get_app_logs "${device_id}" 100)
if echo "${logs}" | grep -q "DNP-REACTIVATION\|recovery\|missed"; then
print_success "Recovery activity detected in logs"
else
print_warn "No recovery activity detected in logs"
print_info "This may indicate recovery is not yet implemented (expected for Phase 1)"
fi
wait_for_ui_action "Verify in the app UI that:
1. The notification is still scheduled (check 'Scheduled Notifications' screen)
2. Any missed notifications are marked as missed
3. Future notifications are verified/rescheduled
Press Enter when verification is complete."
print_success "TEST 1: Cold Start Recovery - Manual verification required"
fi
# ============================================
# TEST 2: Notification Persistence (Swipe from App Switcher)
# ============================================
if should_run_test "2" "$@"; then
print_header "TEST 2: Notification Persistence (App Termination)"
echo "Purpose: Verify that notifications persist when app is terminated"
echo " (iOS OS-guaranteed behavior)."
echo ""
wait_for_user
print_step "1" "Schedule a notification..."
launch_app
ensure_permissions
wait_for_ui_action "In the app UI, schedule a daily notification for a future time."
sleep 2
print_step "2" "Terminate app..."
print_info "Terminating app (simulating swipe from app switcher)..."
device_id=$(get_simulator_id)
xcrun simctl terminate "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || true
print_info "App terminated. Waiting 3 seconds..."
sleep 3
print_step "3" "Verify notification still exists..."
print_info "On iOS, notifications persist automatically (OS-guaranteed)."
print_info "The notification should still fire even though the app is terminated."
wait_for_ui_action "Verify that:
1. The notification fires at the scheduled time (even though app is terminated)
2. When you tap the notification, the app launches
3. The notification is marked as delivered
Press Enter when verification is complete."
print_success "TEST 2: Notification Persistence - iOS OS-guaranteed behavior verified"
fi
# ============================================
# TEST 3: Invalid Data Handling
# ============================================
if should_run_test "3" "$@"; then
print_header "TEST 3: Invalid Data Handling"
echo "Purpose: Verify that the plugin handles invalid data gracefully"
echo " without crashing."
echo ""
wait_for_user
print_step "1" "Test invalid notification time..."
launch_app
ensure_permissions
wait_for_ui_action "In the app UI, try to schedule a notification with invalid data:
1. Empty time string
2. Invalid time format (e.g., '25:00' or '12:99')
3. Negative time values
The app should show an error message and NOT crash."
print_info "Checking logs for error handling..."
sleep 2
device_id=$(get_simulator_id)
logs=$(get_app_logs "${device_id}" 50)
# Debug: Show captured logs if verbose (uncomment to debug)
# echo "DEBUG: Captured logs:"
# echo "${logs}"
# Check for error patterns (case-insensitive):
# - error/invalid keywords
# - DNP-* error prefixes (plugin error logs)
# - invalid_time_format (error code)
# - ERROR MESSAGE (Capacitor bridge prefix, if it appears in system logs)
if echo "${logs}" | grep -qiE "error|invalid|DNP-.*(error|fail|reject)|invalid_time_format|ERROR MESSAGE"; then
print_success "Error handling detected in logs"
else
print_info "No errors in recent system logs (may indicate graceful handling)"
print_warn "Note: Capacitor bridge errors (⚡️ logs in Xcode console) may not appear in system logs."
print_info "The plugin uses call.reject() which logs to Xcode console, not system logs."
print_info "Verify error handling by checking:"
print_info " 1. Xcode console for ⚡️ ERROR MESSAGE logs"
print_info " 2. App UI shows error messages (not crashes)"
print_info " 3. Valid notifications still work after errors"
fi
wait_for_ui_action "Verify that:
1. Invalid data is rejected with clear error messages
2. The app does NOT crash
3. Valid notifications can still be scheduled after errors
Press Enter when verification is complete."
print_success "TEST 3: Invalid Data Handling - Manual verification required"
fi
# ============================================
# Summary
# ============================================
print_header "Phase 1 Testing Complete"
echo "All Phase 1 tests have been executed."
echo ""
echo "Note: iOS recovery features (ReactivationManager) are NOT yet implemented."
echo " Tests 1 and 2 will show expected behavior once recovery is implemented."
echo ""
echo "Next Steps:"
echo " 1. Review test results"
echo " 2. Check logs for any errors"
echo " 3. Implement recovery features (Phase 1 directive)"
echo " 4. Re-run tests after implementation"
echo ""