Phase 1 User Acceptance Test Guide
Phase 1 User Acceptance Test Guide
Purpose: Verify that the Content Export/Import system works correctly in real-world scenarios.
Duration: ~15 minutes
Prerequisites:
- Local development environment running
- Database with some categories and tools (or run
php artisan db:seed)
Test 1: Export Command Basic Functionality
Steps:
- Check current database state:
php artisan tinker --execute="echo 'Categories: ' . App\Models\Category::count() . PHP_EOL; echo 'Tools: ' . App\Models\Tool::count();"
✅ Expected: Shows count of categories and tools in your database.
- Run export command:
php artisan content:export
✅ Expected output:
Exporting content...
Exported categories 12
Exported tools 87
Saved to F:\code\aimanifesto\database\content\snapshots\snapshot-2025-10-22.json
Updated F:\code\aimanifesto\database\content\latest.json
Export completed in 1.23 seconds
- Verify snapshot file exists:
ls -la database/content/snapshots/
✅ Expected: See a file named snapshot-YYYY-MM-DD.json
- Verify latest.json exists:
ls -la database/content/latest.json
✅ Expected: File exists and was just created/updated
Test 2: Examine Export File Structure
Steps:
- Open the snapshot file:
# Windows
notepad database/content/latest.json
# Mac/Linux
cat database/content/latest.json | head -50
- Verify JSON structure:
✅ Check for these sections:
{
"metadata": {
"version": "1.0",
"exported_at": "...",
"schema_version": "1.0",
"record_counts": { ... }
},
"categories": [ ... ],
"tools": [ ... ]
}
- Verify category data:
# Count categories in JSON (should match database)
php -r "\$data = json_decode(file_get_contents('database/content/latest.json'), true); echo count(\$data['categories']);"
✅ Expected: Number matches your database count
- Verify tool data includes category slugs:
# Check first tool's category relationship
php -r "\$data = json_decode(file_get_contents('database/content/latest.json'), true); echo \$data['tools'][0]['category_slug'];"
✅ Expected: Shows a category slug (not a numeric ID)
Test 3: Import Command - Fresh Database
Steps:
- Backup current database state:
php artisan content:export --output=database/content/backup-before-test.json
- Record current counts:
php artisan tinker --execute="echo 'Categories: ' . App\Models\Category::count() . PHP_EOL; echo 'Tools: ' . App\Models\Tool::count();"
- Delete all data (⚠️ destructive - local only!):
php artisan tinker --execute="App\Models\Tool::query()->delete(); App\Models\Category::query()->delete(); echo 'Deleted all data';"
- Verify database is empty:
php artisan tinker --execute="echo 'Categories: ' . App\Models\Category::count() . PHP_EOL; echo 'Tools: ' . App\Models\Tool::count();"
✅ Expected: Both show 0
- Import from latest snapshot:
php artisan content:import
✅ Expected output:
Importing content from database/content/latest.json...
Validating snapshot...
Schema version 1.0 (compatible)
Exported at 2025-10-22 14:30:00
Processing categories...
Created 12
Updated 0
Skipped 0
Processing tools...
Created 87
Updated 0
Skipped 0
Import completed in 3.45 seconds
- Verify data was restored:
php artisan tinker --execute="echo 'Categories: ' . App\Models\Category::count() . PHP_EOL; echo 'Tools: ' . App\Models\Tool::count();"
✅ Expected: Counts match original numbers
- Verify relationships work:
php artisan tinker --execute="echo App\Models\Tool::with('category')->first()->name . ' → ' . App\Models\Tool::first()->category->name;"
✅ Expected: Shows a tool name and its category name (relationship intact)
Test 4: Import Command - Upsert Behavior
Steps:
- Run import again (on top of existing data):
php artisan content:import
✅ Expected output:
Processing categories...
Created 0
Updated 12 ← Should update, not duplicate!
Skipped 0
Processing tools...
Created 0
Updated 87 ← Should update, not duplicate!
Skipped 0
- Verify no duplicates were created:
php artisan tinker --execute="echo 'Categories: ' . App\Models\Category::count() . PHP_EOL; echo 'Tools: ' . App\Models\Tool::count();"
✅ Expected: Same counts as before (no duplicates)
- Test updating existing data:
# Modify a category name in database
php artisan tinker --execute="App\Models\Category::first()->update(['name' => 'MODIFIED NAME']); echo 'Modified first category';"
# Import should revert it
php artisan content:import
# Check if name was reverted
php artisan tinker --execute="echo App\Models\Category::first()->name;"
✅ Expected: Name reverted to original value from snapshot
- Test that views_count is preserved:
# Set high views count on a tool
php artisan tinker --execute="App\Models\Tool::first()->update(['views_count' => 9999]); echo 'Set views to 9999';"
# Import should preserve it
php artisan content:import
# Check views count
php artisan tinker --execute="echo 'Views: ' . App\Models\Tool::first()->views_count;"
✅ Expected: Shows 9999 (views_count preserved during import)
Test 5: Custom Export Path
Steps:
- Export to custom location:
php artisan content:export --output=storage/app/custom-export.json
- Verify file created:
ls -la storage/app/custom-export.json
✅ Expected: File exists at custom location
- Import from custom file:
php artisan content:import storage/app/custom-export.json
✅ Expected: Import succeeds
- Clean up:
rm storage/app/custom-export.json
Test 6: Specific Snapshot Import
Steps:
- Create a tagged snapshot:
php artisan content:export --output=database/content/snapshots/test-v1.0.0.json
- Modify database:
php artisan tinker --execute="App\Models\Category::first()->update(['name' => 'CHANGED']);"
- Import specific snapshot:
php artisan content:import database/content/snapshots/test-v1.0.0.json
- Verify restoration:
php artisan tinker --execute="echo App\Models\Category::first()->name;"
✅ Expected: Original name restored
- Clean up:
rm database/content/snapshots/test-v1.0.0.json
Test 7: Error Handling
Steps:
- Test with missing file:
php artisan content:import database/content/nonexistent.json
✅ Expected: Error message about file not found, command fails gracefully
- Test with invalid JSON:
echo "invalid json {{{" > database/content/test-invalid.json
php artisan content:import database/content/test-invalid.json
✅ Expected: Error message about invalid JSON
- Clean up:
rm database/content/test-invalid.json
Test 8: Production Workflow Simulation
Steps:
- Simulate production export:
# Export current state
php artisan content:export --output=database/content/snapshots/production-2025-10-22.json
- Simulate local import:
# Import the "production" snapshot
php artisan content:import database/content/snapshots/production-2025-10-22.json
- Verify success:
php artisan tinker --execute="echo 'Categories: ' . App\Models\Category::count() . PHP_EOL; echo 'Tools: ' . App\Models\Tool::count();"
✅ Expected: All data present and correct
- Clean up:
rm database/content/snapshots/production-2025-10-22.json
Test 9: JSON Field Integrity
Steps:
- Check that JSON arrays are preserved:
php artisan tinker --execute="
\$tool = App\Models\Tool::whereNotNull('features')->first();
if (\$tool) {
echo 'Tool: ' . \$tool->name . PHP_EOL;
echo 'Features: ' . json_encode(\$tool->features) . PHP_EOL;
echo 'Use Cases: ' . json_encode(\$tool->use_cases) . PHP_EOL;
}
"
✅ Expected: Shows tool with properly formatted JSON arrays
- Export and import:
php artisan content:export
php artisan content:import
- Verify arrays still correct:
php artisan tinker --execute="
\$tool = App\Models\Tool::whereNotNull('features')->first();
if (\$tool) {
echo 'Features count: ' . count(\$tool->features ?? []) . PHP_EOL;
}
"
✅ Expected: Same number of features as before
Test 10: Full Round-Trip with Real Data
Final Integration Test
- Record current state:
# Get checksums of all data
php artisan tinker --execute="
echo 'Category checksum: ' . md5(json_encode(App\Models\Category::orderBy('id')->get()->toArray())) . PHP_EOL;
echo 'Tool checksum: ' . md5(json_encode(App\Models\Tool::orderBy('id')->get()->toArray())) . PHP_EOL;
"
- Export:
php artisan content:export --output=database/content/snapshots/round-trip-test.json
- Delete all data:
php artisan tinker --execute="App\Models\Tool::query()->delete(); App\Models\Category::query()->delete();"
- Import:
php artisan content:import database/content/snapshots/round-trip-test.json
- Verify data integrity:
# Get checksums again (should match original)
php artisan tinker --execute="
echo 'Category checksum: ' . md5(json_encode(App\Models\Category::orderBy('id')->get()->toArray())) . PHP_EOL;
echo 'Tool checksum: ' . md5(json_encode(App\Models\Tool::orderBy('id')->get()->toArray())) . PHP_EOL;
"
✅ Expected: Checksums DIFFER (because views_count is preserved, not exported values)
But verify data manually:
php artisan tinker --execute="
\$original = json_decode(file_get_contents('database/content/snapshots/round-trip-test.json'), true);
\$current = App\Models\Category::count();
echo 'Expected categories: ' . count(\$original['categories']) . PHP_EOL;
echo 'Actual categories: ' . \$current . PHP_EOL;
"
✅ Expected: Numbers match
- Clean up:
rm database/content/snapshots/round-trip-test.json
✅ Success Criteria
All tests should pass with:
- ✅ Export creates valid JSON files
- ✅ Import restores data completely
- ✅ Upsert prevents duplicates
- ✅ Relationships (category_id) maintained correctly
- ✅ JSON arrays preserved
- ✅ Views count preserved on update
- ✅ Error handling works gracefully
- ✅ Custom paths supported
- ✅ Specific snapshot imports work
🐛 If Something Fails
-
Check Laravel logs:
tail -100 storage/logs/laravel.log -
Run automated tests:
php artisan test --filter=Content -
Verify database connection:
php artisan tinker --execute="DB::select('select 1');" -
Check file permissions:
ls -la database/content/
Next Steps After Testing
Once all tests pass:
-
Create your first production snapshot:
php artisan content:export --output=database/content/snapshots/production-baseline.json git add database/content/snapshots/production-baseline.json git commit -m "Production baseline snapshot" -
Deploy to production and verify:
# SSH to production server php artisan content:export # Verify export works -
Test production → local sync:
# Download from production scp user@server:/path/to/latest.json database/content/ # Import locally php artisan content:import
📋 Test Results
| Test | Status | Notes |
|---|---|---|
| Export Basic | ⬜ | |
| Export Structure | ⬜ | |
| Import Fresh DB | ⬜ | |
| Import Upsert | ⬜ | |
| Custom Path | ⬜ | |
| Specific Snapshot | ⬜ | |
| Error Handling | ⬜ | |
| Production Workflow | ⬜ | |
| JSON Integrity | ⬜ | |
| Round Trip | ⬜ |
Tester: _______________ Date: _______________ Overall Result: ⬜ Pass / ⬜ Fail