- Normalize alarm time seconds to :00 for consistent comparison - Compare dates (YYYY-MM-DD) instead of full timestamps to detect rollover - Expand logcat search patterns to catch all rollover logs (DN|RESCHEDULE, etc.) - Add 5-second wait after notification fire to allow rollover processing - UI: Normalize seconds display to :00 in all time displays - UI: Add auto-refresh mechanism that detects nextNotificationTime changes - UI: Poll every 3 seconds and force refresh when rollover detected - UI: Initialize tracking variable on page load for change detection Fixes issue where test passed but alarm time didn't actually change, and UI wasn't updating to show rescheduled notification time after rollover.
1698 lines
67 KiB
Bash
Executable File
1698 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.
|
|
capture_logcat "test0_after_rollover" "DN|RESCHEDULE|DN|DISPLAY|DN|RECEIVE|ROLLOVER|DNP-SCHEDULE" 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 "$@"
|
|
|