- Add detailed logging to loadPluginStatus() with [UI Refresh] prefix - Add detailed logging to checkNotificationDelivery() with [Poll] prefix - Log status check results, change detection, and refresh triggers - Log nextNotificationTime comparisons to debug rollover detection - Include Capacitor/Console in logcat capture pattern to capture JS logs - Log notification delivery detection and time calculations - Log when rollover is detected and UI refresh is triggered This enables debugging of UI auto-refresh mechanism and visibility into JavaScript console logs in captured logcat output.
1699 lines
67 KiB
Bash
Executable File
1699 lines
67 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Phase 1 Testing Script - Interactive Test Runner
|
|
# Guides through all Phase 1 tests with clear prompts for UI interaction
|
|
|
|
# Strict mode (P0.2)
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
# Source shared library
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/alarm-test-lib.sh"
|
|
|
|
# Initialize run directory (P1)
|
|
ensure_run_dir || {
|
|
error "Failed to initialize run directory"
|
|
exit 1
|
|
}
|
|
|
|
# Phase 1 specific configuration
|
|
VERIFY_FIRE=${VERIFY_FIRE:-false} # Set VERIFY_FIRE=true to enable alarm fire verification
|
|
APP_DIR="${SCRIPT_DIR}"
|
|
PROJECT_ROOT="$(cd "${APP_DIR}/../.." && pwd)"
|
|
|
|
# Allow selecting specific tests on the command line (e.g. ./test-phase1.sh 1 2)
|
|
SELECTED_TESTS=()
|
|
|
|
check_plugin_configured() {
|
|
print_info "Checking if plugin is already configured..."
|
|
|
|
# Wait a moment for app to fully load
|
|
sleep 3
|
|
|
|
# Check if database exists (indicates plugin has been used)
|
|
DB_EXISTS=$($ADB_BIN shell run-as "${APP_ID}" ls databases/ 2>/dev/null | grep -c "daily_notification" || echo "0")
|
|
|
|
# Check if SharedPreferences has configuration (more reliable)
|
|
# The plugin stores config in SharedPreferences
|
|
PREFS_EXISTS=$($ADB_BIN shell run-as "${APP_ID}" ls shared_prefs/ 2>/dev/null | grep -c "DailyNotification" || echo "0")
|
|
|
|
# Check recent logs for configuration activity
|
|
RECENT_CONFIG=$($ADB_BIN logcat -d -t 50 | grep -E "Plugin configured|configurePlugin|Configuration" | tail -3)
|
|
|
|
if [ "${DB_EXISTS}" -gt "0" ] || [ "${PREFS_EXISTS}" -gt "0" ]; then
|
|
print_success "Plugin appears to be configured (database or preferences exist)"
|
|
|
|
# Show user what to check in UI
|
|
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 either shows ❌ or 'Not configured', you'll need to click 'Configure Plugin'."
|
|
echo ""
|
|
|
|
return 0
|
|
else
|
|
print_info "Plugin not configured (no database or preferences found)"
|
|
print_info "You will need to click 'Configure Plugin' in the app UI"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
check_permissions() {
|
|
print_info "Checking notification permissions..."
|
|
|
|
# Check if POST_NOTIFICATIONS permission is granted (Android 13+)
|
|
# Strip whitespace/newlines to ensure we get a single integer
|
|
PERM_CHECK=$($ADB_BIN shell dumpsys package "${APP_ID}" | grep -A 5 "granted=true" | grep -c "android.permission.POST_NOTIFICATIONS" 2>/dev/null | tr -d '\n\r ' || echo "0")
|
|
|
|
# Also check via app's permission status if available
|
|
PERM_GRANTED=false
|
|
# Ensure PERM_CHECK is a valid integer (strip any remaining non-digits)
|
|
PERM_CHECK=$(echo "${PERM_CHECK}" | tr -d '\n\r ' | grep -oE '^[0-9]+$' || echo "0")
|
|
if [ "${PERM_CHECK}" -gt "0" ] 2>/dev/null; then
|
|
PERM_GRANTED=true
|
|
else
|
|
# Check if we're on Android 12 or below (permission not required)
|
|
SDK_VERSION=$($ADB_BIN shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\n\r ' || echo "0")
|
|
SDK_VERSION=$(echo "${SDK_VERSION}" | grep -oE '^[0-9]+$' || echo "0")
|
|
if [ "${SDK_VERSION}" -lt "33" ] 2>/dev/null; then
|
|
PERM_GRANTED=true # Pre-Android 13 doesn't need runtime permission
|
|
fi
|
|
fi
|
|
|
|
if [ "${PERM_GRANTED}" = true ]; then
|
|
print_success "Notification permissions granted"
|
|
return 0
|
|
else
|
|
print_warn "Notification permissions may not be granted"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Comprehensive prerequisite verification (aligned with PHASE1_TEST0_GOLDEN.md)
|
|
# Verifies all 5 items specified in the golden run document:
|
|
# 1. Plugin Settings: Configured
|
|
# 2. Native Fetcher: Configured
|
|
# 3. Notifications: Granted
|
|
# 4. Exact Alarms: Granted
|
|
# 5. Channel: Enabled (High)
|
|
#
|
|
# Usage: verify_all_prerequisites "phase1" "phase1_test0" "p1_t0_s2"
|
|
# This will verify all prerequisites and emit step events for each check
|
|
verify_all_prerequisites() {
|
|
local phase_id="${1:-}"
|
|
local test_id="${2:-}"
|
|
local base_step_id="${3:-}"
|
|
|
|
info "Verifying all prerequisites (aligned with golden run specification)..."
|
|
|
|
local all_ok=true
|
|
local missing_items=()
|
|
|
|
# 1. Check Plugin Settings: Configured
|
|
local step_id="${base_step_id}_plugin"
|
|
set_test_context "${phase_id}" "${test_id}" "${step_id}"
|
|
step_start "${step_id}" "Verifying Plugin Settings: Configured"
|
|
if check_plugin_configured >/dev/null 2>&1; then
|
|
ok "Plugin Settings: ✅ Configured"
|
|
step_pass "${step_id}" "Plugin Settings configured"
|
|
else
|
|
warn "Plugin Settings: ❌ Not configured"
|
|
all_ok=false
|
|
missing_items+=("Plugin Settings")
|
|
step_fail "${step_id}" "Plugin Settings not configured"
|
|
fi
|
|
|
|
# 2. Check Native Fetcher: Configured
|
|
# (Plugin config check also verifies native fetcher via SharedPreferences)
|
|
# For explicit verification, we check logs for fetcher configuration
|
|
step_id="${base_step_id}_fetcher"
|
|
set_test_context "${phase_id}" "${test_id}" "${step_id}"
|
|
step_start "${step_id}" "Verifying Native Fetcher: Configured"
|
|
local fetcher_configured=false
|
|
local fetcher_logs=$($ADB_BIN logcat -d -t 100 | grep -E "Native fetcher.*registered|FETCHER.*CONFIGURE|configureNativeFetcher" | tail -5 || true)
|
|
if [ -n "${fetcher_logs}" ] || check_plugin_configured >/dev/null 2>&1; then
|
|
ok "Native Fetcher: ✅ Configured"
|
|
step_pass "${step_id}" "Native Fetcher configured"
|
|
fetcher_configured=true
|
|
else
|
|
warn "Native Fetcher: ⚠️ Status unclear (check UI)"
|
|
step_warn "${step_id}" "Native Fetcher status unclear - verify in UI"
|
|
# Don't fail on this - let UI verification catch it
|
|
fi
|
|
|
|
# 3. Check Notifications: Granted
|
|
step_id="${base_step_id}_notifications"
|
|
set_test_context "${phase_id}" "${test_id}" "${step_id}"
|
|
step_start "${step_id}" "Verifying Notifications: Granted"
|
|
if check_permissions >/dev/null 2>&1; then
|
|
ok "Notifications: ✅ Granted"
|
|
step_pass "${step_id}" "Notifications permission granted"
|
|
else
|
|
error "Notifications: ❌ Not granted"
|
|
all_ok=false
|
|
missing_items+=("Notifications permission")
|
|
step_fail "${step_id}" "Notifications permission not granted"
|
|
fi
|
|
|
|
# 4. Check Exact Alarms: Granted
|
|
# Best way: Check if app has alarms with exactAllowReason=policy_permission
|
|
# This indicates the permission is granted and working
|
|
step_id="${base_step_id}_exact_alarms"
|
|
set_test_context "${phase_id}" "${test_id}" "${step_id}"
|
|
step_start "${step_id}" "Verifying Exact Alarms: Granted"
|
|
local exact_alarm_granted=false
|
|
local sdk_version=$($ADB_BIN shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\n\r ' | grep -oE '^[0-9]+$' || echo '0')
|
|
|
|
if [ "${sdk_version}" -ge "31" ] 2>/dev/null; then
|
|
# Android 12+ requires SCHEDULE_EXACT_ALARM permission
|
|
# Check if app has any alarms with exactAllowReason=policy_permission
|
|
# This is the most reliable indicator that exact alarms are working
|
|
local alarm_dump=$($ADB_BIN shell dumpsys alarm 2>/dev/null | grep -A 10 "${APP_ID}" | grep -c "exactAllowReason=policy_permission" || echo "0")
|
|
alarm_dump=$(echo "${alarm_dump}" | tr -d '\n\r ' | grep -oE '^[0-9]+$' || echo "0")
|
|
|
|
if [ "${alarm_dump}" -gt "0" ] 2>/dev/null; then
|
|
# Found alarms with policy_permission - exact alarms are working
|
|
exact_alarm_granted=true
|
|
else
|
|
# No alarms yet, but check if permission is declared in manifest
|
|
# (We can't easily check runtime permission status via ADB, so we'll rely on UI)
|
|
local has_perm=$($ADB_BIN shell dumpsys package "${APP_ID}" | grep -c "android.permission.SCHEDULE_EXACT_ALARM" 2>/dev/null | tr -d '\n\r ' || echo "0")
|
|
has_perm=$(echo "${has_perm}" | grep -oE '^[0-9]+$' || echo "0")
|
|
if [ "${has_perm}" -gt "0" ] 2>/dev/null; then
|
|
# Permission is declared, assume it's granted (will be verified in UI)
|
|
exact_alarm_granted=true
|
|
fi
|
|
fi
|
|
else
|
|
# Pre-Android 12 doesn't require this permission
|
|
exact_alarm_granted=true
|
|
fi
|
|
|
|
if [ "${exact_alarm_granted}" = true ]; then
|
|
ok "Exact Alarms: ✅ Granted"
|
|
step_pass "${step_id}" "Exact Alarms permission granted"
|
|
else
|
|
warn "Exact Alarms: ⚠️ May not be granted (check UI)"
|
|
step_warn "${step_id}" "Exact Alarms permission unclear - verify in UI"
|
|
# Don't fail - let UI verification catch it
|
|
fi
|
|
|
|
# 5. Check Channel: Enabled (High)
|
|
# Channel status is best verified via UI, but we can check logs
|
|
step_id="${base_step_id}_channel"
|
|
set_test_context "${phase_id}" "${test_id}" "${step_id}"
|
|
step_start "${step_id}" "Verifying Channel: Enabled (High)"
|
|
local channel_logs=$($ADB_BIN logcat -d -t 100 | grep -E "Channel.*enabled|channel.*importance|NotificationChannel" | tail -5 || true)
|
|
if [ -n "${channel_logs}" ]; then
|
|
ok "Channel: ✅ Status found in logs (verify 'Enabled (High)' in UI)"
|
|
step_pass "${step_id}" "Channel status verified"
|
|
else
|
|
info "Channel: ⚠️ Verify 'Enabled (High)' status in UI"
|
|
step_warn "${step_id}" "Channel status unclear - verify 'Enabled (High)' in UI"
|
|
# Don't fail - channel is created automatically
|
|
fi
|
|
|
|
# Final UI verification prompt (aligned with golden run step 4)
|
|
if [ "${MODE}" != "ci" ]; then
|
|
echo ""
|
|
info "=== Final UI Verification (Golden Run Step 4) ==="
|
|
info "Please confirm in the app UI that ALL of the following show ✅:"
|
|
echo " ⚙️ Plugin Settings: ✅ Configured"
|
|
echo " 🔌 Native Fetcher: ✅ Configured"
|
|
echo " 🔔 Notifications: ✅ Granted"
|
|
echo " ⏰ Exact Alarms: ✅ Granted"
|
|
echo " 📢 Channel: ✅ Enabled (High)"
|
|
echo ""
|
|
|
|
if [ "${all_ok}" = false ]; then
|
|
error "Some prerequisites are missing: ${missing_items[*]}"
|
|
ui_prompt "Please fix the missing prerequisites in the app UI, then press Enter to continue."
|
|
else
|
|
ui_prompt "If all 5 items show ✅ in the UI, press Enter to continue. Otherwise, fix any issues first."
|
|
fi
|
|
fi
|
|
|
|
if [ "${all_ok}" = false ] && [ "${MODE}" = "ci" ]; then
|
|
error "Prerequisites not met: ${missing_items[*]}"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
ensure_permissions() {
|
|
if check_permissions; then
|
|
print_success "Permissions already granted"
|
|
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."
|
|
|
|
# Re-check permissions
|
|
sleep 2
|
|
if check_permissions; then
|
|
print_success "Permissions granted"
|
|
return 0
|
|
else
|
|
print_warn "Permissions may still not be granted - continuing anyway"
|
|
print_info "If notifications fail, you may need to grant permissions manually"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
ensure_plugin_configured() {
|
|
if check_plugin_configured; then
|
|
# Plugin might be configured, but let user verify
|
|
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
|
|
}
|
|
|
|
# kill_app is now provided by the library, but Phase 1 uses it with PACKAGE variable
|
|
# The library version uses APP_ID, which is set to PACKAGE, so it should work
|
|
# But we keep this as a compatibility wrapper if needed
|
|
|
|
# Phase 1 specific helper: get_current_time (used for fire verification)
|
|
get_current_time() {
|
|
$ADB_BIN shell date +%s
|
|
}
|
|
|
|
# ========================================
|
|
# CLI Mode Parsing (P2.1)
|
|
# ========================================
|
|
|
|
parse_cli_args() {
|
|
local mode="all"
|
|
SELECTED_TESTS=()
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--setup)
|
|
mode="setup"
|
|
shift
|
|
;;
|
|
--run)
|
|
mode="run"
|
|
shift
|
|
;;
|
|
--smoke)
|
|
mode="smoke"
|
|
shift
|
|
;;
|
|
--all)
|
|
mode="all"
|
|
shift
|
|
;;
|
|
--ci)
|
|
mode="ci"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
[0-9]*)
|
|
SELECTED_TESTS+=("$1")
|
|
shift
|
|
;;
|
|
*)
|
|
error "Unknown option: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo "$mode"
|
|
}
|
|
|
|
show_help() {
|
|
cat <<EOF
|
|
Usage: $0 [MODE] [TEST_IDS...]
|
|
|
|
Modes:
|
|
--setup Run setup only (permissions, app install, plugin config checks)
|
|
--run Run actual Phase 1 tests (requires setup first)
|
|
--smoke Minimal test: schedule one notification + verify pending
|
|
--all Run setup + all tests (default)
|
|
--ci Non-interactive: automated checks only, fail fast
|
|
|
|
Test Selection:
|
|
[TEST_IDS] Optional test numbers to run (e.g., 0 1 2)
|
|
If omitted, all tests run
|
|
|
|
Examples:
|
|
$0 # Run all (setup + tests)
|
|
$0 --setup # Setup only
|
|
$0 --run # Run tests only
|
|
$0 --smoke # Quick smoke test
|
|
$0 --ci # CI mode (non-interactive)
|
|
$0 --run 1 2 # Run only tests 1 and 2
|
|
$0 --smoke 0 # Run smoke test for test 0
|
|
|
|
EOF
|
|
}
|
|
|
|
# ========================================
|
|
# Setup Mode (P2.1)
|
|
# ========================================
|
|
|
|
run_setup() {
|
|
section "Phase 1 Setup"
|
|
|
|
info "This mode performs pre-flight checks and setup:"
|
|
echo " • ADB device connection"
|
|
echo " • Emulator readiness"
|
|
echo " • App build and installation"
|
|
echo " • Permission checks"
|
|
echo " • Plugin configuration checks"
|
|
echo ""
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
# Pre-flight checks
|
|
section "Pre-Flight Checks"
|
|
require_adb_device
|
|
|
|
# Build and install
|
|
build_app
|
|
install_app
|
|
|
|
# Clear logs
|
|
clear_logs
|
|
|
|
# Launch app for setup checks
|
|
launch_app
|
|
sleep 2
|
|
|
|
# Check permissions and plugin configuration together
|
|
section "Permissions & Plugin Configuration"
|
|
info "Checking both permissions and plugin/fetcher configuration..."
|
|
echo ""
|
|
|
|
# Check both silently first
|
|
local perms_ok=false
|
|
local plugin_ok=false
|
|
|
|
if check_permissions >/dev/null 2>&1; then
|
|
perms_ok=true
|
|
fi
|
|
|
|
if check_plugin_configured >/dev/null 2>&1; then
|
|
plugin_ok=true
|
|
fi
|
|
|
|
# Show status
|
|
if [ "${perms_ok}" = "true" ]; then
|
|
ok "Notification permissions: ✅ Granted"
|
|
else
|
|
warn "Notification permissions: ❌ Not granted"
|
|
fi
|
|
|
|
if [ "${plugin_ok}" = "true" ]; then
|
|
ok "Plugin configuration: ✅ Configured"
|
|
else
|
|
warn "Plugin configuration: ❌ Not configured"
|
|
fi
|
|
|
|
# If either needs attention, show single combined prompt
|
|
if [ "${MODE}" = "ci" ]; then
|
|
if [ "${perms_ok}" != "true" ]; then
|
|
error "Permissions not granted - cannot proceed in CI mode"
|
|
exit 1
|
|
fi
|
|
if [ "${plugin_ok}" != "true" ]; then
|
|
error "Plugin not configured - cannot proceed in CI mode"
|
|
exit 1
|
|
fi
|
|
else
|
|
# Show single combined prompt for both
|
|
if [ "${perms_ok}" != "true" ] || [ "${plugin_ok}" != "true" ]; then
|
|
ui_prompt "Please configure in the app UI:
|
|
|
|
REQUIRED STATUS (all must show ✅):
|
|
1) 🔔 Notifications: ✅ Granted
|
|
2) ⚙️ Plugin Settings: ✅ Configured
|
|
3) 🔌 Native Fetcher: ✅ Configured
|
|
|
|
ACTIONS:
|
|
- If Notifications shows ❌: Click 'Request Permissions' button
|
|
- If Plugin Settings or Native Fetcher show ❌: Click 'Configure Plugin' button
|
|
- Wait for all three to show ✅
|
|
|
|
Once all three show ✅, press Enter to continue."
|
|
|
|
# Re-check both after user action
|
|
sleep 2
|
|
echo ""
|
|
info "Re-checking after configuration..."
|
|
perms_ok=false
|
|
plugin_ok=false
|
|
if check_permissions >/dev/null 2>&1; then
|
|
perms_ok=true
|
|
ok "Notification permissions: ✅ Verified"
|
|
else
|
|
warn "Notification permissions: ⚠️ Still not granted"
|
|
fi
|
|
if check_plugin_configured >/dev/null 2>&1; then
|
|
plugin_ok=true
|
|
ok "Plugin configuration: ✅ Verified"
|
|
else
|
|
warn "Plugin configuration: ⚠️ Still not configured"
|
|
fi
|
|
|
|
echo ""
|
|
if [ "${perms_ok}" = "true" ] && [ "${plugin_ok}" = "true" ]; then
|
|
ok "All checks passed - ready to proceed"
|
|
else
|
|
warn "Some items may still need configuration - continuing anyway"
|
|
info "If tests fail, verify all three items show ✅ in the app UI"
|
|
fi
|
|
else
|
|
# Both are OK, but still prompt for final verification
|
|
ui_prompt "Please verify in the app UI that you see:
|
|
|
|
1) 🔔 Notifications: ✅ Granted
|
|
2) ⚙️ Plugin Settings: ✅ Configured
|
|
3) 🔌 Native Fetcher: ✅ Configured
|
|
|
|
If all show ✅, press Enter to continue.
|
|
If any show ❌, configure them first, then press Enter."
|
|
fi
|
|
fi
|
|
|
|
ok "Setup complete!"
|
|
|
|
# Capture initial state
|
|
capture_alarms "setup_initial"
|
|
capture_logcat "setup_initial" "DNP" 100
|
|
|
|
evidence_block "phase1_setup"
|
|
}
|
|
|
|
# ========================================
|
|
# Smoke Test Mode (P2.1)
|
|
# ========================================
|
|
|
|
run_smoke_test() {
|
|
section "Phase 1 Smoke Test"
|
|
|
|
info "Minimal test: schedule one notification + verify pending"
|
|
echo "Expected time: 2-4 minutes"
|
|
echo ""
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
# Ensure setup is done
|
|
if ! check_permissions >/dev/null 2>&1; then
|
|
warn "Setup not complete - running setup first..."
|
|
run_setup
|
|
fi
|
|
|
|
# Capture before state
|
|
capture_alarms "smoke_before"
|
|
|
|
# Schedule notification
|
|
section "Smoke Test: Schedule Notification"
|
|
launch_app
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
ui_prompt "1) In the app UI, click 'Test Notification' to schedule a notification for 4 minutes in the future."
|
|
else
|
|
info "CI mode: Assuming notification is scheduled manually or via automation"
|
|
sleep 3
|
|
fi
|
|
|
|
# Wait for alarm to be registered
|
|
sleep 3
|
|
|
|
# Capture after state
|
|
capture_alarms "smoke_after"
|
|
capture_logcat "smoke_after" "DNP-SCHEDULE|DNP-NOTIFY" 50
|
|
|
|
# Verify alarm exists
|
|
local alarm_count
|
|
alarm_count="$(get_plugin_alarm_count)"
|
|
|
|
if [ "${alarm_count}" -eq "1" ]; then
|
|
verdict_pass "phase1_smoke" "Smoke test passed: 1 alarm scheduled"
|
|
elif [ "${alarm_count}" -gt "1" ]; then
|
|
verdict_fail "phase1_smoke" "Smoke test failed: ${alarm_count} alarms found (expected: 1)"
|
|
else
|
|
verdict_warn "phase1_smoke" "Smoke test inconclusive: ${alarm_count} alarms found (expected: 1, may be race condition)"
|
|
fi
|
|
|
|
evidence_block "phase1_smoke"
|
|
}
|
|
|
|
# ========================================
|
|
# Main Test Execution
|
|
# ========================================
|
|
|
|
main() {
|
|
# Parse CLI arguments
|
|
MODE="$(parse_cli_args "$@")"
|
|
|
|
section "Phase 1 Testing Script"
|
|
echo "Mode: ${MODE}"
|
|
echo "Run ID: ${RUN_ID}"
|
|
echo "Evidence directory: $(get_run_dir)"
|
|
echo ""
|
|
|
|
if [ "${MODE}" = "setup" ]; then
|
|
run_setup
|
|
return 0
|
|
elif [ "${MODE}" = "smoke" ]; then
|
|
run_smoke_test
|
|
return 0
|
|
elif [ "${MODE}" = "ci" ]; then
|
|
info "CI mode: Non-interactive, automated checks only"
|
|
run_setup
|
|
# In CI mode, we might want to run a subset of tests
|
|
# For now, just run smoke test
|
|
run_smoke_test
|
|
return 0
|
|
fi
|
|
|
|
# For --run and --all modes, continue with full test suite
|
|
if [ "${MODE}" = "all" ]; then
|
|
info "Running full test suite (setup + tests)"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
run_setup
|
|
elif [ "${MODE}" = "run" ]; then
|
|
info "Running tests only (assuming setup already done)"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
fi
|
|
|
|
# ============================================
|
|
# TEST 0: Daily Rollover (Core Contract Verification)
|
|
# ============================================
|
|
if should_run_test "0" SELECTED_TESTS; then
|
|
section "TEST 0: Daily Rollover Verification"
|
|
|
|
# Set test context for event emission
|
|
set_test_context "phase1" "phase1_test0" ""
|
|
|
|
info "Purpose: Verify that after a notification fires, the next day's"
|
|
info " schedule is correctly computed and only ONE alarm exists."
|
|
info ""
|
|
info "Expected time: 5-10 minutes"
|
|
info "Automatable: Partial (requires manual clock manipulation or waiting)"
|
|
echo ""
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
# Capture initial state
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s1"
|
|
step_start "p1_t0_s1" "Capturing initial state"
|
|
capture_alarms "test0_initial"
|
|
capture_logcat "test0_initial" "DNP" 50
|
|
step_pass "p1_t0_s1" "Initial state captured"
|
|
|
|
substep "Step 1: Verify setup and schedule a test notification"
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s2"
|
|
step_start "p1_t0_s2" "Verifying prerequisites and scheduling test notification"
|
|
launch_app
|
|
|
|
# Comprehensive prerequisite verification (aligned with PHASE1_TEST0_GOLDEN.md step 4)
|
|
# Verifies all 5 items: Plugin Settings, Native Fetcher, Notifications, Exact Alarms, Channel
|
|
verify_all_prerequisites "phase1" "phase1_test0" "p1_t0_s2"
|
|
|
|
# If prerequisites failed and we're in CI mode, exit
|
|
if [ $? -ne 0 ] && [ "${MODE}" = "ci" ]; then
|
|
error "Prerequisites not met - cannot proceed with Test 0"
|
|
exit 1
|
|
fi
|
|
|
|
# Capture pre-schedule state
|
|
capture_alarms "test0_before_schedule"
|
|
capture_screenshot "test0_before_schedule"
|
|
|
|
local initial_count system_count
|
|
initial_count="$(get_plugin_alarm_count)"
|
|
system_count="$(get_system_alarm_count)"
|
|
info "Current notification alarms: ${initial_count} (expected before scheduling: 0)"
|
|
info "System/other alarms: ${system_count} (for context)"
|
|
|
|
if [ "${initial_count}" -eq "0" ] 2>/dev/null; then
|
|
ok "No existing notification alarms (clean state)"
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
ui_prompt "1) In the app UI, schedule a daily notification.
|
|
|
|
For this test, schedule it for a time very soon (e.g., 1-2 minutes from now)
|
|
to observe the rollover behavior.
|
|
|
|
This will schedule:
|
|
• 1 notification alarm (AlarmManager) for the specified time
|
|
• 1 prefetch job (WorkManager) for 2 minutes before that time"
|
|
else
|
|
info "CI mode: Assuming notification is scheduled manually"
|
|
sleep 3
|
|
fi
|
|
|
|
sleep 3 # Give alarm time to be registered
|
|
|
|
# Capture post-schedule state
|
|
capture_alarms "test0_after_schedule"
|
|
capture_screenshot "test0_after_schedule"
|
|
|
|
local post_schedule_count
|
|
post_schedule_count="$(get_plugin_alarm_count)"
|
|
|
|
# Preliminary check (final verdict comes after rollover)
|
|
if [ "${post_schedule_count}" -eq "0" ] 2>/dev/null; then
|
|
warn "Found 0 plugin alarms right after scheduling (may be race condition)"
|
|
info "Alarm may not be visible in dumpsys yet - will verify after rollover"
|
|
elif [ "${post_schedule_count}" -eq "1" ] 2>/dev/null; then
|
|
ok "Found 1 notification alarm (expected: 1) - preliminary check passed"
|
|
else
|
|
warn "Found ${post_schedule_count} notification alarms (expected: 1) - DUPLICATES DETECTED!"
|
|
error "This indicates duplicate NOTIFICATION alarms were created (BUG)"
|
|
show_plugin_alarms_compact
|
|
fi
|
|
initial_count="${post_schedule_count}"
|
|
fi
|
|
|
|
# Show alarm details if we found exactly 1 alarm
|
|
local initial_alarm_time=""
|
|
if [ "${initial_count}" -eq "1" ] 2>/dev/null; then
|
|
ok "Single notification alarm scheduled (one per day)"
|
|
|
|
local alarm_details
|
|
alarm_details="$($ADB_BIN shell dumpsys alarm | grep -A 3 "com.timesafari.dailynotification" | grep -A 3 "com.timesafari.daily.NOTIFICATION" | head -10)"
|
|
if [ -n "${alarm_details}" ]; then
|
|
info "Notification alarm details:"
|
|
echo "${alarm_details}" | head -5
|
|
echo ""
|
|
# Extract initial alarm time for rollover verification (normalize seconds to 00)
|
|
initial_alarm_time=$(echo "${alarm_details}" | grep -oE "origWhen=[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}" | head -1 | sed 's/origWhen=//' | sed 's/:[0-9][0-9]$/:00/')
|
|
if [ -n "${initial_alarm_time}" ]; then
|
|
info "Initial alarm time: ${initial_alarm_time} (normalized)"
|
|
fi
|
|
fi
|
|
elif [ "${initial_count}" -gt "1" ] 2>/dev/null; then
|
|
warn "Found ${initial_count} notification alarms (expected: 1) - DUPLICATES DETECTED!"
|
|
fi
|
|
|
|
step_pass "p1_t0_s2" "Notification scheduled"
|
|
|
|
substep "Step 2: Wait for notification to fire"
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s3"
|
|
step_start "p1_t0_s3" "Waiting for notification to fire"
|
|
info "The test will automatically verify rollover after the notification fires."
|
|
echo ""
|
|
echo "What will be verified automatically:"
|
|
echo " • Alarm count (should be exactly 1)"
|
|
echo " • Alarm time changed (should be 24 hours later)"
|
|
echo " • Rollover logs present"
|
|
echo ""
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
ui_prompt "Please wait for the notification to fire (or advance the emulator clock past the scheduled time).
|
|
|
|
The test will automatically verify:
|
|
• Only ONE alarm exists (one per day)
|
|
• Alarm time advanced to tomorrow (24 hours later)
|
|
• Rollover occurred (logs will be checked)
|
|
|
|
Press Enter after the notification has fired (or after advancing the clock)."
|
|
else
|
|
info "CI mode: Waiting for notification to fire..."
|
|
sleep 5 # Give time for rollover if automated
|
|
fi
|
|
|
|
step_pass "p1_t0_s3" "Notification fired (or time advanced)"
|
|
|
|
substep "Step 3: Verify rollover state"
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s4"
|
|
step_start "p1_t0_s4" "Waiting for rollover to complete"
|
|
info "Waiting for rollover to complete (notification should auto-schedule next day's alarm)..."
|
|
info "Allowing up to ~15 seconds for Android to process rollover and update alarms..."
|
|
|
|
# Wait longer for rollover to complete (notification fires → rollover schedules next day)
|
|
sleep 5 # Give Android time to process the notification fire and rollover
|
|
|
|
local post_rollover_count system_final
|
|
post_rollover_count="$(wait_for_stable_plugin_alarm_count 5 2)"
|
|
system_final="$(get_system_alarm_count)"
|
|
|
|
# Capture post-rollover state
|
|
capture_alarms "test0_after_rollover"
|
|
# Look for rollover logs: DN|RESCHEDULE, DN|DISPLAY, DN|RECEIVE, ROLLOVER_ON_FIRE, etc.
|
|
# Also capture JavaScript console logs (Capacitor/Console) for UI debugging
|
|
capture_logcat "test0_after_rollover" "DN|RESCHEDULE|DN|DISPLAY|DN|RECEIVE|ROLLOVER|DNP-SCHEDULE|Capacitor/Console" 200
|
|
capture_screenshot "test0_after_rollover"
|
|
|
|
step_pass "p1_t0_s4" "Post-rollover evidence captured"
|
|
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s6"
|
|
step_start "p1_t0_s6" "Verifying schedule state"
|
|
|
|
info "Notification alarms after rollover: ${post_rollover_count} (expected: 1)"
|
|
info "System/other alarms: ${system_final} (for context)"
|
|
|
|
# Extract post-rollover alarm time and verify it changed
|
|
local post_rollover_alarm_time=""
|
|
local rollover_verified=false
|
|
if [ "${post_rollover_count}" -eq "1" ] 2>/dev/null; then
|
|
local post_alarm_details
|
|
post_alarm_details="$($ADB_BIN shell dumpsys alarm | grep -A 3 "com.timesafari.dailynotification" | grep -A 3 "com.timesafari.daily.NOTIFICATION" | head -10)"
|
|
if [ -n "${post_alarm_details}" ]; then
|
|
# Extract post-rollover alarm time (normalize seconds to 00 for comparison)
|
|
post_rollover_alarm_time=$(echo "${post_alarm_details}" | grep -oE "origWhen=[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}" | head -1 | sed 's/origWhen=//' | sed 's/:[0-9][0-9]$/:00/')
|
|
if [ -n "${post_rollover_alarm_time}" ]; then
|
|
info "Post-rollover alarm time: ${post_rollover_alarm_time} (normalized)"
|
|
|
|
# Verify alarm time changed (rollover occurred)
|
|
# Compare dates only (YYYY-MM-DD) to detect day change
|
|
if [ -n "${initial_alarm_time}" ] && [ -n "${post_rollover_alarm_time}" ]; then
|
|
local initial_date=$(echo "${initial_alarm_time}" | cut -d' ' -f1)
|
|
local post_date=$(echo "${post_rollover_alarm_time}" | cut -d' ' -f1)
|
|
|
|
if [ "${initial_date}" != "${post_date}" ]; then
|
|
ok "Alarm date changed: ${initial_alarm_time} → ${post_rollover_alarm_time}"
|
|
rollover_verified=true
|
|
else
|
|
warn "Alarm date did NOT change: ${post_rollover_alarm_time} (same date as initial: ${initial_date})"
|
|
warn "This indicates the notification did not fire and rollover did not occur"
|
|
rollover_verified=false
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Check for rollover logs (wider search including all possible rollover patterns)
|
|
# Look for: ROLLOVER_ON_FIRE source, RESCHEDULE logs, DNP-SCHEDULE with ROLLOVER, etc.
|
|
# Note: DN|RESCHEDULE uses pipe character, so we need to escape it properly
|
|
local rollover_logs=$($ADB_BIN logcat -d -t 1000 | grep -E "ROLLOVER_ON_FIRE|source=ROLLOVER|DNP-SCHEDULE.*ROLLOVER|DN\|RESCHEDULE|DN\|RESCHEDULE_OK|DN\|RESCHEDULE_START|Scheduling next.*notification|scheduleNextNotification|Next notification scheduled" | tail -15 || true)
|
|
if [ -n "${rollover_logs}" ]; then
|
|
ok "Rollover logs found - notification fired and rollover occurred"
|
|
info "Rollover log excerpt:"
|
|
echo "${rollover_logs}" | head -3 | sed 's/^/ /'
|
|
rollover_verified=true
|
|
else
|
|
warn "No rollover logs found - checking if notification fired at all..."
|
|
# Check for any notification display logs
|
|
local notification_logs=$($ADB_BIN logcat -d -t 500 | grep -E "DN\|DISPLAY_NOTIF|Notification.*displayed|com.timesafari.daily.NOTIFICATION" | tail -5 || true)
|
|
if [ -n "${notification_logs}" ]; then
|
|
warn "Notification display logs found, but no rollover logs - rollover may have failed"
|
|
info "Notification log excerpt:"
|
|
echo "${notification_logs}" | head -2 | sed 's/^/ /'
|
|
else
|
|
warn "No notification logs found - notification may not have fired"
|
|
fi
|
|
if [ "${rollover_verified}" = false ]; then
|
|
warn "⚠️ Rollover verification failed: alarm date unchanged AND no rollover logs"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Final verdict
|
|
if [ "${post_rollover_count}" -eq "1" ] 2>/dev/null && [ "${rollover_verified}" = true ]; then
|
|
step_pass "p1_t0_s6" "Schedule state verified - exactly 1 alarm, rollover confirmed"
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s7"
|
|
verdict_pass "test0_daily_rollover" "Daily rollover created exactly one NOTIFICATION alarm for tomorrow"
|
|
elif [ "${post_rollover_count}" -eq "1" ] 2>/dev/null && [ "${rollover_verified}" = false ]; then
|
|
step_warn "p1_t0_s6" "Alarm count correct but rollover not verified"
|
|
warn "Found 1 alarm but time did not change - notification may not have fired"
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s7"
|
|
verdict_warn "test0_daily_rollover" "Alarm count correct (1) but rollover not verified - alarm time unchanged, no rollover logs"
|
|
elif [ "${post_rollover_count}" -gt "1" ] 2>/dev/null; then
|
|
step_fail "p1_t0_s6" "Duplicate alarms detected"
|
|
error "Daily rollover created ${post_rollover_count} NOTIFICATION alarms (duplicates)"
|
|
show_plugin_alarms_compact
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s7"
|
|
verdict_fail "test0_daily_rollover" "Duplicate alarms detected after rollover (expected: 1, got: ${post_rollover_count})"
|
|
else
|
|
step_warn "p1_t0_s6" "No alarm found after rollover"
|
|
warn "No NOTIFICATION alarm found after rollover (expected: 1, got: 0)"
|
|
show_plugin_alarms_compact
|
|
set_test_context "phase1" "phase1_test0" "p1_t0_s7"
|
|
verdict_warn "test0_daily_rollover" "Rollover may have failed - no alarm found (expected: 1, got: 0)"
|
|
fi
|
|
|
|
evidence_block "test0_daily_rollover"
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
fi # End of "if should_run_test 0" block
|
|
|
|
# ============================================
|
|
# TEST 1: Force-Stop Recovery - Database Restoration
|
|
# ============================================
|
|
if should_run_test "1" SELECTED_TESTS; then
|
|
section "TEST 1: Force-Stop Recovery - Database Restoration"
|
|
|
|
# Set test context
|
|
set_test_context "phase1" "phase1_test1" ""
|
|
|
|
info "Purpose: Verify that after force-stop (which clears alarms), recovery"
|
|
info " uses the database to rebuild alarms on app relaunch."
|
|
info ""
|
|
info "Expected time: 5-8 minutes"
|
|
info "Automatable: Partial (requires manual force-stop verification)"
|
|
echo ""
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
# Capture initial state
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s1"
|
|
step_start "p1_t1_s1" "Clean start - verify no lingering alarms"
|
|
capture_alarms "test1_initial"
|
|
capture_logcat "test1_initial" "DNP" 50
|
|
local lingering_count system_count
|
|
lingering_count="$(get_plugin_alarm_count)"
|
|
system_count="$(get_system_alarm_count)"
|
|
info "Current plugin notification alarms: ${lingering_count}"
|
|
info "System/other alarms: ${system_count} (for context)"
|
|
|
|
local goto_test1_end=false
|
|
|
|
if [ "${lingering_count}" -gt "0" ] 2>/dev/null; then
|
|
warn "Found ${lingering_count} lingering plugin alarm(s) - these will interfere with TEST 1"
|
|
info "TEST 1 needs a clean state (no existing plugin alarms)"
|
|
info "Resetting app state via uninstall + reinstall..."
|
|
|
|
# Uninstall existing app
|
|
info "Uninstalling existing app..."
|
|
set +e
|
|
local uninstall_output uninstall_status
|
|
uninstall_output="$($ADB_BIN uninstall "$APP_ID" 2>&1)"
|
|
uninstall_status=$?
|
|
set -e
|
|
|
|
if [ $uninstall_status -eq 0 ]; then
|
|
ok "App uninstall succeeded (clean slate)"
|
|
else
|
|
if grep -q "DELETE_FAILED_INTERNAL_ERROR" <<<"$uninstall_output"; then
|
|
info "No existing app to uninstall (continuing)"
|
|
else
|
|
warn "App uninstall reported an error: $uninstall_output (continuing anyway)"
|
|
fi
|
|
fi
|
|
|
|
# Reinstall APK
|
|
info "Reinstalling APK..."
|
|
if $ADB_BIN install -r "$APK_PATH" >/dev/null 2>&1; then
|
|
ok "App reinstall succeeded"
|
|
else
|
|
error "App reinstall FAILED - cannot proceed with TEST 1"
|
|
warn "Marking TEST 1 as INCONCLUSIVE due to dirty starting state"
|
|
capture_screenshot "test1_dirty_state_reinstall_failed"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
goto_test1_end=true
|
|
fi
|
|
|
|
if [ "${goto_test1_end}" != "true" ]; then
|
|
# Verify install
|
|
if ! $ADB_BIN shell pm list packages | grep -q "$APP_ID"; then
|
|
error "App not found in package list after reinstall - aborting TEST 1"
|
|
capture_screenshot "test1_dirty_state_verify_failed"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
goto_test1_end=true
|
|
fi
|
|
fi
|
|
|
|
if [ "${goto_test1_end}" != "true" ]; then
|
|
# Clear logs
|
|
info "Clearing logcat buffer after reinstall..."
|
|
$ADB_BIN logcat -c || true
|
|
|
|
# Re-check plugin alarms
|
|
info "Rechecking plugin alarms after reset..."
|
|
sleep 2
|
|
lingering_count="$(get_plugin_alarm_count)"
|
|
info "Plugin alarms after reset: ${lingering_count} (expected: 0)"
|
|
|
|
if [ "${lingering_count}" -ne "0" ] 2>/dev/null; then
|
|
warn "TEST 1 starting with non-zero alarm count even after reset; treating as INCONCLUSIVE"
|
|
info "This may indicate device-specific behavior where alarms persist across uninstall"
|
|
capture_screenshot "test1_unexpected_alarms_after_reset"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
goto_test1_end=true
|
|
fi
|
|
fi
|
|
|
|
if [ "${goto_test1_end}" = "true" ]; then
|
|
warn "TEST 1 skipped due to inability to achieve clean starting state"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
else
|
|
ok "App state reset complete. TEST 1 starting from clean state"
|
|
fi
|
|
else
|
|
ok "No lingering plugin alarms found (clean state)"
|
|
fi
|
|
|
|
step_pass "p1_t1_s1" "Clean state verified"
|
|
|
|
# Skip remaining TEST 1 steps if we couldn't achieve clean state
|
|
if [ "${goto_test1_end}" != "true" ]; then
|
|
|
|
substep "Step 2: Schedule a known future alarm"
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s2"
|
|
step_start "p1_t1_s2" "Scheduling notification"
|
|
launch_app
|
|
ensure_plugin_configured
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
ui_prompt "1) In the app UI, click the 'Test Notification' button.
|
|
|
|
This will schedule ONE notification for 4 minutes in the future.
|
|
(App supports one alarm per day)
|
|
|
|
The alarm will be stored in the database AND scheduled in AlarmManager."
|
|
else
|
|
info "CI mode: Assuming notification is scheduled manually"
|
|
sleep 3
|
|
fi
|
|
|
|
sleep 2
|
|
|
|
# Capture before force-stop state
|
|
capture_alarms "test1_before_force_stop"
|
|
capture_logcat "test1_before_force_stop" "DNP-SCHEDULE|DNP-NOTIFY" 50
|
|
|
|
step_pass "p1_t1_s2" "Notification scheduled"
|
|
|
|
substep "Step 3: Verify alarm exists in AlarmManager (BEFORE force-stop)"
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s3"
|
|
step_start "p1_t1_s3" "Verifying alarm before force-stop"
|
|
local alarm_count_before system_count_before
|
|
alarm_count_before="$(get_plugin_alarm_count)"
|
|
system_count_before="$(get_system_alarm_count)"
|
|
info "Plugin alarms: ${alarm_count_before} (expected: 1)"
|
|
info "System/other alarms: ${system_count_before} (for context)"
|
|
|
|
if [ "${alarm_count_before}" -eq "0" ] 2>/dev/null; then
|
|
error "No plugin alarms found in AlarmManager - cannot test force-stop recovery"
|
|
info "Make sure you clicked 'Test Notification' and wait a moment"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
# Re-check
|
|
alarm_count_before="$(get_plugin_alarm_count)"
|
|
if [ "${alarm_count_before}" -eq "0" ] 2>/dev/null; then
|
|
error "Still no alarms found - aborting test"
|
|
verdict_fail "test1_force_stop_recovery" "No alarms found before force-stop - cannot test recovery"
|
|
evidence_block "test1_force_stop_recovery"
|
|
goto_test1_end=true
|
|
fi
|
|
fi
|
|
|
|
if [ "${alarm_count_before}" -eq "1" ] 2>/dev/null; then
|
|
ok "Single plugin alarm confirmed in AlarmManager (one per day)"
|
|
else
|
|
warn "Found ${alarm_count_before} plugin alarms (expected: 1) - continuing anyway"
|
|
fi
|
|
|
|
# Extract trigger time for later verification
|
|
local alarm_details_before alarm_trigger_ms alarm_trigger_sec alarm_readable
|
|
alarm_details_before="$($ADB_BIN shell dumpsys alarm | grep -A 3 "$APP_ID" | head -10)"
|
|
if [ -n "${alarm_details_before}" ]; then
|
|
info "Alarm details:"
|
|
echo "${alarm_details_before}" | head -5
|
|
echo ""
|
|
fi
|
|
|
|
alarm_trigger_ms=$(echo "${alarm_details_before}" | grep -oE "origWhen [0-9]+" | head -1 | awk '{print $2}')
|
|
if [ -n "${alarm_trigger_ms}" ]; then
|
|
alarm_trigger_sec=$((alarm_trigger_ms / 1000))
|
|
alarm_readable=$(date -d "@${alarm_trigger_sec}" 2>/dev/null || echo "${alarm_trigger_sec}")
|
|
info "Alarm scheduled for: ${alarm_readable} (${alarm_trigger_ms} ms)"
|
|
fi
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
substep "Step 4: Force-stop the app (clears alarms)"
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s3"
|
|
step_start "p1_t1_s3" "Force-stopping app"
|
|
warn "Force-stop will clear ALL alarms from AlarmManager"
|
|
info "Executing: $ADB_BIN shell am force-stop ${APP_ID}"
|
|
force_stop_app
|
|
sleep 2
|
|
|
|
# Capture after force-stop state
|
|
capture_alarms "test1_after_force_stop"
|
|
|
|
substep "Step 5: Verify alarms are MISSING (cleared by OS)"
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s4"
|
|
step_start "p1_t1_s4" "Verifying alarms cleared"
|
|
sleep 1
|
|
local alarm_count_after system_count_after
|
|
alarm_count_after="$(get_plugin_alarm_count)"
|
|
system_count_after="$(get_system_alarm_count)"
|
|
info "Plugin alarms after force-stop: ${alarm_count_after} (expected: 0)"
|
|
info "System/other alarms: ${system_count_after} (for context)"
|
|
|
|
if [ "${alarm_count_after}" -eq "0" ] 2>/dev/null; then
|
|
ok "Plugin alarms cleared by force-stop (count: ${alarm_count_after})"
|
|
info "This confirms: Force-stop cleared alarms from AlarmManager"
|
|
step_pass "p1_t1_s4" "Alarms cleared by force-stop"
|
|
else
|
|
warn "Plugin alarms still present after force-stop (count: ${alarm_count_after})"
|
|
info "Some devices/OS versions may not clear alarms on force-stop"
|
|
info "Continuing test anyway - recovery should still work"
|
|
step_warn "p1_t1_s4" "Alarms not cleared (device-specific behavior)"
|
|
fi
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
substep "Step 6: Relaunch app (triggers recovery from database)"
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s4"
|
|
step_start "p1_t1_s4" "Relaunching app to trigger recovery"
|
|
clear_logs
|
|
launch_app
|
|
sleep 4 # Give recovery time to run
|
|
|
|
substep "Step 7: Verify recovery rebuilt alarms from database"
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s4"
|
|
step_start "p1_t1_s4" "Verifying recovery rebuilt alarms"
|
|
sleep 2
|
|
|
|
# Capture after recovery state
|
|
capture_alarms "test1_after_recovery"
|
|
capture_logcat "test1_after_recovery" "DNP-REACTIVATION" 100
|
|
capture_screenshot "test1_after_recovery"
|
|
|
|
local alarm_count_recovered system_count_recovered
|
|
alarm_count_recovered="$(get_plugin_alarm_count)"
|
|
system_count_recovered="$(get_system_alarm_count)"
|
|
info "Plugin alarms after recovery: ${alarm_count_recovered} (expected: 1)"
|
|
info "System/other alarms: ${system_count_recovered} (for context)"
|
|
|
|
info "Checking recovery logs..."
|
|
check_recovery_logs
|
|
|
|
info "Expected log output:"
|
|
echo " DNP-REACTIVATION: Starting app launch recovery"
|
|
echo " DNP-REACTIVATION: Rescheduled alarm: <id> for <time>"
|
|
echo " DNP-REACTIVATION: Cold start recovery complete: ... rescheduled>=1, ..."
|
|
echo ""
|
|
|
|
# Check recovery logs for rescheduling and recovery source
|
|
local recovery_result rescheduled_count verified_count recovery_source
|
|
recovery_result="$($ADB_BIN logcat -d | grep "Cold start recovery complete\|Boot recovery complete" | tail -1)"
|
|
rescheduled_count=$(echo "${recovery_result}" | grep -oE "rescheduled=[0-9]+" | grep -oE "[0-9]+" || echo "0")
|
|
verified_count=$(echo "${recovery_result}" | grep -oE "verified=[0-9]+" | grep -oE "[0-9]+" || echo "0")
|
|
recovery_source="$($ADB_BIN logcat -d | grep -E "recovery source|from database|DATABASE" | tail -1 || echo "")"
|
|
|
|
# Pass/fail criteria
|
|
local test1_passed=false
|
|
|
|
if [ "${alarm_count_recovered}" -gt "0" ] 2>/dev/null; then
|
|
ok "Alarms restored in AlarmManager (count: ${alarm_count_recovered})"
|
|
if [ "${rescheduled_count}" -gt "0" ] 2>/dev/null; then
|
|
ok "Recovery logs confirm rescheduling (rescheduled=${rescheduled_count})"
|
|
test1_passed=true
|
|
else
|
|
warn "Alarms restored but logs show rescheduled=0"
|
|
info "This might be okay if alarms were verified instead of rescheduled"
|
|
# Still pass if alarms are restored, even if rescheduled=0
|
|
test1_passed=true
|
|
fi
|
|
else
|
|
error "No alarms restored (count: ${alarm_count_recovered})"
|
|
info "Recovery may have failed or alarms were not in database"
|
|
fi
|
|
|
|
# Final verdict
|
|
if [ "${test1_passed}" = "true" ]; then
|
|
step_pass "p1_t1_s4" "Recovery successful"
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s5"
|
|
verdict_pass "test1_force_stop_recovery" "Recovery successfully rebuilt alarms from database (before: ${alarm_count_before}, after stop: ${alarm_count_after}, after recovery: ${alarm_count_recovered}, rescheduled: ${rescheduled_count})"
|
|
else
|
|
step_fail "p1_t1_s4" "Recovery failed"
|
|
set_test_context "phase1" "phase1_test1" "p1_t1_s5"
|
|
verdict_fail "test1_force_stop_recovery" "Recovery did not rebuild alarms correctly (before: ${alarm_count_before}, after stop: ${alarm_count_after}, after recovery: ${alarm_count_recovered})"
|
|
fi
|
|
|
|
evidence_block "test1_force_stop_recovery"
|
|
|
|
# Optional: Verify alarm fires (controlled by VERIFY_FIRE flag)
|
|
if [ "${VERIFY_FIRE}" = "true" ] && [ -n "${alarm_trigger_ms}" ] && [ "${goto_test1_end}" != "true" ]; then
|
|
substep "Step 8: Verify alarm fires at scheduled time (optional)"
|
|
|
|
local current_time_sec current_time_ms wait_ms wait_sec
|
|
current_time_sec=$(get_current_time)
|
|
current_time_ms=$((current_time_sec * 1000))
|
|
wait_ms=$((alarm_trigger_ms - current_time_ms))
|
|
|
|
if [ "${wait_ms}" -lt 0 ]; then
|
|
warn "Alarm time already passed (${wait_ms} ms ago); skipping fire verification"
|
|
else
|
|
wait_sec=$((wait_ms / 1000))
|
|
|
|
# Clamp upper bound to prevent accidentally waiting 30+ minutes
|
|
if [ "${wait_sec}" -gt 600 ]; then
|
|
warn "Alarm is >10 minutes away (${wait_sec}s); skipping fire verification"
|
|
info "To test fire verification, schedule alarm closer to current time"
|
|
else
|
|
info "Alarm scheduled for: ${alarm_readable}"
|
|
info "Current time: $(date -d "@${current_time_sec}" 2>/dev/null || echo "${current_time_sec}")"
|
|
info "Waiting ~${wait_sec} seconds for alarm to fire..."
|
|
|
|
clear_logs
|
|
sleep ${wait_sec}
|
|
sleep 2
|
|
|
|
info "Checking logs for fired alarm..."
|
|
local alarm_fired
|
|
alarm_fired="$($ADB_BIN logcat -d | grep -E "DNP-RECEIVE|DNP-NOTIFY|DNP-WORK|Alarm fired|Notification displayed" | tail -10)"
|
|
|
|
if [ -n "${alarm_fired}" ]; then
|
|
ok "Alarm fired! Logs:"
|
|
echo "${alarm_fired}"
|
|
else
|
|
warn "No alarm fire logs found"
|
|
info "Check notification tray manually or review recent logs"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
fi # End of "if goto_test1_end != true" block
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
fi # End of "if should_run_test 1" block
|
|
|
|
# ============================================
|
|
# TEST 2: Schedule Update (One-Per-Day Semantics)
|
|
# ============================================
|
|
if should_run_test "2" SELECTED_TESTS; then
|
|
section "TEST 2: Schedule Update Verification"
|
|
|
|
# Set test context
|
|
set_test_context "phase1" "phase1_test2" ""
|
|
|
|
info "Purpose: Verify that updating the schedule time maintains 'one per day' semantics"
|
|
info ""
|
|
info "Expected time: 4-6 minutes"
|
|
info "Automatable: Partial (requires manual schedule update)"
|
|
echo ""
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
# Capture initial state
|
|
set_test_context "phase1" "phase1_test2" "p1_t2_s1"
|
|
step_start "p1_t2_s1" "Schedule initial notification"
|
|
capture_alarms "test2_initial"
|
|
capture_logcat "test2_initial" "DNP" 50
|
|
|
|
substep "Step 1: Launch app and verify initial schedule"
|
|
launch_app
|
|
ensure_plugin_configured
|
|
|
|
# Get initial alarm count
|
|
local initial_alarm_count system_alarm_count
|
|
initial_alarm_count="$(get_plugin_alarm_count)"
|
|
system_alarm_count="$(get_system_alarm_count)"
|
|
info "Plugin alarms: ${initial_alarm_count} (expected: 1)"
|
|
info "System/other alarms: ${system_alarm_count} (for context)"
|
|
|
|
if [ "${initial_alarm_count}" -eq "1" ] 2>/dev/null; then
|
|
ok "Initial alarm confirmed (one per day)"
|
|
step_pass "p1_t2_s1" "Initial notification scheduled"
|
|
elif [ "${initial_alarm_count}" -eq "0" ] 2>/dev/null; then
|
|
warn "No initial alarm found - scheduling one first..."
|
|
if [ "${MODE}" != "ci" ]; then
|
|
ui_prompt "1) In the app UI, schedule a daily notification (e.g., click 'Test Notification')."
|
|
else
|
|
info "CI mode: Assuming notification is scheduled manually"
|
|
sleep 2
|
|
fi
|
|
sleep 2
|
|
initial_alarm_count="$(get_plugin_alarm_count)"
|
|
if [ "${initial_alarm_count}" -eq "1" ] 2>/dev/null; then
|
|
ok "Alarm scheduled"
|
|
step_pass "p1_t2_s1" "Initial notification scheduled"
|
|
else
|
|
error "Failed to schedule initial alarm"
|
|
step_fail "p1_t2_s1" "Failed to schedule"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
fi
|
|
else
|
|
warn "Found ${initial_alarm_count} plugin alarms (expected: 1) - continuing anyway"
|
|
step_warn "p1_t2_s1" "Unexpected alarm count"
|
|
fi
|
|
|
|
# Capture before update state
|
|
capture_alarms "test2_before_update"
|
|
|
|
substep "Step 2: Update schedule time"
|
|
set_test_context "phase1" "phase1_test2" "p1_t2_s2"
|
|
step_start "p1_t2_s2" "Update schedule"
|
|
if [ "${MODE}" != "ci" ]; then
|
|
ui_prompt "2) In the app UI, change the schedule time (e.g., from 06:50 to 07:10)
|
|
and apply the schedule.
|
|
|
|
This should cancel the old alarm and schedule a new one at the new time.
|
|
You should still have exactly 1 alarm (one per day)."
|
|
else
|
|
info "CI mode: Assuming schedule is updated manually"
|
|
sleep 3
|
|
fi
|
|
|
|
sleep 3
|
|
|
|
# Capture after update state
|
|
capture_alarms "test2_after_update"
|
|
capture_logcat "test2_after_update" "DNP-SCHEDULE|DNP-NOTIFY" 50
|
|
|
|
local updated_alarm_count
|
|
updated_alarm_count="$(get_plugin_alarm_count)"
|
|
info "Plugin alarms after update: ${updated_alarm_count} (expected: 1)"
|
|
|
|
local test2_failed=false
|
|
|
|
if [ "${updated_alarm_count}" -eq "1" ] 2>/dev/null; then
|
|
ok "Single alarm confirmed after schedule update (one per day maintained)"
|
|
step_pass "p1_t2_s2" "Schedule updated"
|
|
elif [ "${updated_alarm_count}" -gt "1" ] 2>/dev/null; then
|
|
error "TEST 2 FAILED: Found ${updated_alarm_count} plugin alarms after update (expected: 1)"
|
|
error "Old alarm was NOT canceled - violates 'one per day' semantics"
|
|
info "This indicates the plugin did not clean up existing schedules before creating new one"
|
|
test2_failed=true
|
|
step_fail "p1_t2_s2" "Multiple alarms detected"
|
|
else
|
|
warn "Found ${updated_alarm_count} plugin alarms (expected: 1) - no alarm scheduled after update"
|
|
step_warn "p1_t2_s2" "No alarm after update"
|
|
fi
|
|
|
|
substep "Step 3: Kill app and relaunch (triggers recovery)"
|
|
set_test_context "phase1" "phase1_test2" "p1_t2_s3"
|
|
step_start "p1_t2_s3" "Verify one-per-day semantics"
|
|
kill_app
|
|
clear_logs
|
|
launch_app
|
|
|
|
substep "Step 4: Check recovery logs and verify alarm count"
|
|
sleep 3
|
|
|
|
# Capture after recovery state
|
|
capture_alarms "test2_after_recovery"
|
|
capture_logcat "test2_after_recovery" "DNP-REACTIVATION" 100
|
|
|
|
check_recovery_logs
|
|
|
|
info "Expected log output (either):"
|
|
echo " DNP-REACTIVATION: Verified scheduled alarm: <id> at <time>"
|
|
echo " OR"
|
|
echo " DNP-REACTIVATION: Rescheduled missing alarm: <id> at <time>"
|
|
echo " DNP-REACTIVATION: Cold start recovery complete: ..., verified=1 or rescheduled=1, ..."
|
|
echo ""
|
|
|
|
local recovery_result rescheduled_count verified_count alarm_count_after_recovery
|
|
recovery_result="$($ADB_BIN logcat -d | grep "Cold start recovery complete" | tail -1)"
|
|
rescheduled_count=$(echo "${recovery_result}" | grep -oE "rescheduled=[0-9]+" | grep -oE "[0-9]+" || echo "0")
|
|
verified_count=$(echo "${recovery_result}" | grep -oE "verified=[0-9]+" | grep -oE "[0-9]+" || echo "0")
|
|
alarm_count_after_recovery="$(get_plugin_alarm_count)"
|
|
info "Plugin alarms after recovery: ${alarm_count_after_recovery} (expected: 1)"
|
|
|
|
# CRITICAL: Test fails if alarm count > 1 after recovery (violates "one per day")
|
|
if [ "${alarm_count_after_recovery}" -gt "1" ] 2>/dev/null; then
|
|
error "TEST 2 FAILED: Found ${alarm_count_after_recovery} plugin alarms after recovery (expected: 1)"
|
|
error "Multiple schedules in database caused recovery to reschedule duplicates"
|
|
error "This violates 'one per day' semantics - only one notification per day should exist"
|
|
test2_failed=true
|
|
step_fail "p1_t2_s3" "Multiple alarms after recovery"
|
|
elif [ "${alarm_count_after_recovery}" -eq "1" ] 2>/dev/null; then
|
|
step_pass "p1_t2_s3" "One-per-day semantics verified"
|
|
else
|
|
step_warn "p1_t2_s3" "Inconclusive alarm count"
|
|
fi
|
|
|
|
# Final verdict
|
|
set_test_context "phase1" "phase1_test2" "p1_t2_s4"
|
|
if [ "${test2_failed}" = "true" ]; then
|
|
verdict_fail "test2_schedule_update" "Multiple alarms detected - violates 'one per day' semantics (after update: ${updated_alarm_count}, after recovery: ${alarm_count_after_recovery})"
|
|
elif [ "${alarm_count_after_recovery}" -eq "1" ] 2>/dev/null; then
|
|
if [ "${rescheduled_count}" -gt "0" ] 2>/dev/null; then
|
|
verdict_pass "test2_schedule_update" "Schedule update maintained one per day semantics (rescheduled: ${rescheduled_count})"
|
|
elif [ "${verified_count}" -gt "0" ] 2>/dev/null; then
|
|
verdict_pass "test2_schedule_update" "Schedule update maintained one per day semantics (verified: ${verified_count})"
|
|
else
|
|
verdict_pass "test2_schedule_update" "Schedule update maintained one per day semantics (no recovery needed)"
|
|
fi
|
|
else
|
|
verdict_warn "test2_schedule_update" "Schedule update result inconclusive (after update: ${updated_alarm_count}, after recovery: ${alarm_count_after_recovery})"
|
|
fi
|
|
|
|
evidence_block "test2_schedule_update"
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
fi # End of "if should_run_test 2" block
|
|
|
|
# ============================================
|
|
# TEST 3: Recovery Timeout
|
|
# ============================================
|
|
if should_run_test "3" SELECTED_TESTS; then
|
|
section "TEST 3: Recovery Timeout"
|
|
|
|
# Set test context
|
|
set_test_context "phase1" "phase1_test3" ""
|
|
|
|
info "Purpose: Verify recovery times out gracefully"
|
|
info ""
|
|
info "Expected time: 2-3 minutes"
|
|
info "Automatable: Yes (code verification only)"
|
|
info "Note: Full test (100+ schedules) requires manual execution"
|
|
echo ""
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
# Capture initial state
|
|
capture_alarms "test3_initial"
|
|
|
|
substep "Step 1: Check recovery timeout implementation"
|
|
set_test_context "phase1" "phase1_test3" "p1_t3_s1"
|
|
step_start "p1_t3_s1" "Check recovery timeout implementation"
|
|
local timeout_found timeout_protection_found
|
|
timeout_found=false
|
|
timeout_protection_found=false
|
|
|
|
if grep -q "RECOVERY_TIMEOUT_SECONDS.*2L" "${PROJECT_ROOT}/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt" 2>/dev/null; then
|
|
ok "Timeout is set to 2 seconds"
|
|
timeout_found=true
|
|
else
|
|
error "Timeout not found in code"
|
|
fi
|
|
|
|
if grep -q "withTimeout" "${PROJECT_ROOT}/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt" 2>/dev/null; then
|
|
ok "Timeout protection is implemented"
|
|
timeout_protection_found=true
|
|
else
|
|
error "Timeout protection not found"
|
|
fi
|
|
|
|
# Capture evidence
|
|
capture_logcat "test3_timeout_check" "DNP-REACTIVATION" 50
|
|
|
|
if [ "${timeout_found}" = "true" ] && [ "${timeout_protection_found}" = "true" ]; then
|
|
step_pass "p1_t3_s1" "Timeout mechanism verified"
|
|
else
|
|
step_fail "p1_t3_s1" "Timeout mechanism not found"
|
|
fi
|
|
|
|
# Final verdict
|
|
set_test_context "phase1" "phase1_test3" "p1_t3_s2"
|
|
if [ "${timeout_found}" = "true" ] && [ "${timeout_protection_found}" = "true" ]; then
|
|
verdict_pass "test3_recovery_timeout" "Timeout mechanism verified in code (2 seconds, withTimeout protection)"
|
|
else
|
|
verdict_fail "test3_recovery_timeout" "Timeout mechanism not properly implemented (timeout found: ${timeout_found}, protection found: ${timeout_protection_found})"
|
|
fi
|
|
|
|
evidence_block "test3_recovery_timeout"
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
fi # End of "if should_run_test 3" block
|
|
|
|
# ============================================
|
|
# TEST 4: Invalid Data Handling
|
|
# ============================================
|
|
if should_run_test "4" SELECTED_TESTS; then
|
|
section "TEST 4: Invalid Data Handling"
|
|
|
|
# Set test context
|
|
set_test_context "phase1" "phase1_test4" ""
|
|
|
|
info "Purpose: Verify invalid data doesn't crash recovery"
|
|
info ""
|
|
info "Expected time: 5-8 minutes"
|
|
info "Automatable: Partial (requires debuggable app or manual injection)"
|
|
info "Note: This test injects invalid data (empty IDs, null nextRunAt) and"
|
|
info " verifies that recovery handles it gracefully without crashing"
|
|
echo ""
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
|
|
# Capture initial state
|
|
set_test_context "phase1" "phase1_test4" "p1_t4_s1"
|
|
step_start "p1_t4_s1" "Inject invalid test data"
|
|
capture_alarms "test4_initial"
|
|
capture_logcat "test4_initial" "DNP" 50
|
|
|
|
substep "Step 1: Inject invalid test data"
|
|
|
|
# Clear logs before test
|
|
clear_logs
|
|
|
|
# Check if app is debuggable (look for DEBUGGABLE flag)
|
|
local is_debuggable=false
|
|
if $ADB_BIN shell dumpsys package "${APP_ID}" | grep -qi "DEBUGGABLE"; then
|
|
is_debuggable=true
|
|
fi
|
|
|
|
if [ "${is_debuggable}" = "true" ]; then
|
|
ok "App is debuggable - can inject data via database"
|
|
|
|
substep "Step 2: Inject invalid data via direct database access"
|
|
|
|
# Launch app first to ensure database is initialized
|
|
info "Launching app to initialize database..."
|
|
$ADB_BIN shell am start -n "${APP_ID}/.MainActivity" > /dev/null 2>&1
|
|
sleep 2
|
|
|
|
# Stop app before database injection (prevents locking issues)
|
|
info "Stopping app before database injection..."
|
|
$ADB_BIN shell am force-stop "${APP_ID}"
|
|
sleep 1
|
|
|
|
# Inject invalid data via direct database access
|
|
info "Injecting invalid test data into database..."
|
|
|
|
# Calculate next run time (24 hours from now in milliseconds)
|
|
local next_run past_time now_time
|
|
next_run=$(($(date +%s) * 1000 + 86400000))
|
|
past_time=$(($(date +%s) * 1000 - 3600000))
|
|
now_time=$(($(date +%s) * 1000))
|
|
|
|
# Inject schedule with empty ID
|
|
info " - Injecting schedule with empty ID..."
|
|
$ADB_BIN shell "run-as ${APP_ID} sqlite3 databases/daily_notification_plugin.db \"INSERT OR REPLACE INTO schedules (id, kind, cron, clockTime, enabled, nextRunAt, jitterMs, backoffPolicy) VALUES ('', 'notify', '0 9 * * *', '09:00', 1, ${next_run}, 0, 'exp');\"" 2>&1 || true
|
|
|
|
# Inject schedule with null nextRunAt
|
|
info " - Injecting schedule with null nextRunAt..."
|
|
$ADB_BIN shell "run-as ${APP_ID} sqlite3 databases/daily_notification_plugin.db \"INSERT OR REPLACE INTO schedules (id, kind, cron, clockTime, enabled, nextRunAt, jitterMs, backoffPolicy) VALUES ('test_null_nextrunat', 'notify', '0 9 * * *', '09:00', 1, NULL, 0, 'exp');\"" 2>&1 || true
|
|
|
|
# Checkpoint WAL file to ensure changes are visible
|
|
info " - Checkpointing WAL file..."
|
|
$ADB_BIN shell "run-as ${APP_ID} sqlite3 databases/daily_notification_plugin.db \"PRAGMA wal_checkpoint(FULL);\"" 2>&1 || true
|
|
|
|
# Verify data was inserted
|
|
info "Verifying data injection..."
|
|
local schedule_count
|
|
schedule_count="$($ADB_BIN shell "run-as ${APP_ID} sqlite3 databases/daily_notification_plugin.db \"SELECT COUNT(*) FROM schedules;\"" 2>&1 | tr -d '\r\n')"
|
|
|
|
info " - Schedules in database: ${schedule_count}"
|
|
|
|
if [ "${schedule_count}" -gt "0" ] 2>/dev/null; then
|
|
ok "Invalid test data injected successfully"
|
|
step_pass "p1_t4_s1" "Invalid data injected"
|
|
else
|
|
warn "No schedules found after injection - data may not have been inserted"
|
|
step_warn "p1_t4_s1" "Data injection may have failed"
|
|
fi
|
|
|
|
substep "Step 3: Trigger recovery with invalid data"
|
|
set_test_context "phase1" "phase1_test4" "p1_t4_s2"
|
|
step_start "p1_t4_s2" "Trigger recovery with invalid data"
|
|
$ADB_BIN shell am start -n "${APP_ID}/.MainActivity" > /dev/null 2>&1
|
|
sleep 3 # Give recovery time to run
|
|
|
|
info "Waiting for recovery to complete..."
|
|
sleep 2
|
|
|
|
# Capture after recovery state
|
|
capture_alarms "test4_after_recovery"
|
|
capture_logcat "test4_after_recovery" "DNP-REACTIVATION|Skipping invalid" 100
|
|
capture_screenshot "test4_after_recovery"
|
|
step_pass "p1_t4_s2" "Recovery triggered"
|
|
|
|
substep "Step 4: Check recovery logs for invalid data handling"
|
|
set_test_context "phase1" "phase1_test4" "p1_t4_s3"
|
|
step_start "p1_t4_s3" "Check recovery logs"
|
|
|
|
# Check logs
|
|
local recovery_logs
|
|
recovery_logs="$($ADB_BIN logcat -d | grep -E "DNP-REACTIVATION|Skipping invalid|TEST:" | tail -30)"
|
|
|
|
echo ""
|
|
info "Recovery logs:"
|
|
echo "${recovery_logs}"
|
|
echo ""
|
|
|
|
# Check for invalid data handling
|
|
local test4_passed=false
|
|
local test4_failed=false
|
|
|
|
if echo "${recovery_logs}" | grep -q "Skipping invalid"; then
|
|
ok "Invalid data was detected and skipped"
|
|
echo "${recovery_logs}" | grep "Skipping invalid"
|
|
test4_passed=true
|
|
else
|
|
warn "No 'Skipping invalid' logs found"
|
|
info "This could mean:"
|
|
echo " - Invalid data wasn't injected (database constraints prevented it)"
|
|
echo " - Recovery didn't encounter invalid data"
|
|
echo " - Logs were cleared"
|
|
fi
|
|
|
|
if echo "${recovery_logs}" | grep -q "recovery complete\|Recovery completed"; then
|
|
ok "Recovery completed successfully"
|
|
if [ "${test4_passed}" = "false" ]; then
|
|
test4_passed=true # Recovery completed = passed (even if no invalid data found)
|
|
fi
|
|
else
|
|
warn "Recovery completion message not found in logs"
|
|
fi
|
|
|
|
if echo "${recovery_logs}" | grep -qiE "crash|fatal|exception.*recovery|Failed.*recovery"; then
|
|
error "TEST 4 FAILED: Recovery crashed or threw fatal exception"
|
|
test4_failed=true
|
|
step_fail "p1_t4_s3" "Recovery crashed"
|
|
else
|
|
step_pass "p1_t4_s3" "Recovery handled invalid data gracefully"
|
|
fi
|
|
|
|
# Final verdict
|
|
set_test_context "phase1" "phase1_test4" "p1_t4_s4"
|
|
if [ "${test4_failed}" = "true" ]; then
|
|
verdict_fail "test4_invalid_data_handling" "Recovery crashed or threw fatal exception when handling invalid data"
|
|
elif [ "${test4_passed}" = "true" ]; then
|
|
verdict_pass "test4_invalid_data_handling" "Recovery handled invalid data gracefully (or no invalid data found)"
|
|
else
|
|
verdict_warn "test4_invalid_data_handling" "Could not verify invalid data handling"
|
|
fi
|
|
|
|
else
|
|
info "App is not debuggable - cannot inject data via database"
|
|
info "TEST 4: Code review confirms invalid data handling exists"
|
|
info " - ReactivationManager.kt checks for empty IDs"
|
|
info " - Errors are logged but don't crash recovery"
|
|
verdict_warn "test4_invalid_data_handling" "App not debuggable - cannot inject invalid data for testing"
|
|
fi
|
|
|
|
evidence_block "test4_invalid_data_handling"
|
|
|
|
if [ "${MODE}" != "ci" ]; then
|
|
pause
|
|
fi
|
|
fi # End of "if should_run_test 4" block
|
|
|
|
# ============================================
|
|
# Summary
|
|
# ============================================
|
|
section "Testing Complete"
|
|
|
|
info "Test Results Summary:"
|
|
echo ""
|
|
echo "All test verdicts are shown above with evidence locations."
|
|
echo "Review evidence in: $(get_run_dir)"
|
|
echo ""
|
|
|
|
info "All recovery logs:"
|
|
echo ""
|
|
$ADB_BIN logcat -d | grep "$REACTIVATION_TAG" | tail -20
|
|
echo ""
|
|
|
|
ok "Phase 1 testing script complete!"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " - Review evidence in: $(get_run_dir)"
|
|
echo " - Verify all test verdicts above"
|
|
echo " - Check database if needed (debuggable app)"
|
|
echo " - Update documentation with test results"
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|
|
|