diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c36cc55..cc9a28f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.0', '8.1', '8.2', '8.3'] + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] name: PHP ${{ matrix.php }} Tests diff --git a/.gitignore b/.gitignore index 18d7a06..7eee5af 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ composer.lock coverage coverage.xml .phpunit.result.cache -tests/Integration/SandboxCredentials.php +tests/Integration/IntegrationCredentials.php .idea/ # Environment files diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5691fe6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,204 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [Unreleased] + +### Added +- PHP 7.4 minimum version support with modern type hints +- Support for PHP 8.0, 8.1, 8.2, 8.3, and 8.4 +- Return type declarations to helper functions in examples +- Comprehensive TESTING.md documentation for application testing +- GitHub Actions workflow for automated testing across PHP 7.4-8.4 +- Code coverage reporting (76.33% coverage) + +### Changed +- Replaced all references to "sandbox" environment with "integration" terminology +- Updated composer.json to require PHP >=7.4 (previously >=8.0) +- Modernized build system with GitHub Actions (replaced Travis CI) +- Updated FamilySearch API endpoint URLs to current subdomains +- Enhanced test suite with 102 tests (74 unit, 28 integration) +- Removed VCR (Video Cassette Recorder) dependencies from testing +- Improved README.md with clearer security warnings and modern PHP examples +- Fixed testPendingModification() to target real endpoints + +### Fixed +- PHP 8.4 compatibility in test suite +- Array syntax error in README.md code example (changed `{}` to `[]`) + +### Security +- Clarified that environment variables must be used for credentials (never hardcode) +- Enhanced security documentation in examples/_includes.php + +## [1.3.0] - 2024 (Master Branch - Not Yet Released) + +### Added +- **Optional AES-256-GCM session token encryption** for production deployments + - New `sessionEncryption` configuration option (defaults to `false` for backward compatibility) + - New `sessionEncryptionKey` configuration parameter for encryption key + - Automatic key normalization supporting base64, hex, and raw binary formats + - Backward-compatible migration from plaintext to encrypted tokens + - Fail-secure behavior (encryption failures never fall back to plaintext) +- Comprehensive SECURITY.md documentation (750+ lines) + - Detailed threat model and security considerations + - Key generation and storage best practices + - Server configuration guidelines + - Production deployment checklist + - Key rotation procedures + - Encryption limitations and use cases +- New security test suite for encryption functionality +- Encryption key validation and error handling + +### Changed +- Updated testing strategy with comprehensive integration and unit tests +- Modernized GitHub Actions CI/CD pipeline (replaced Travis CI) +- Updated documentation links to current FamilySearch developer portal +- Improved test organization (separated unit and integration tests) +- Enhanced code coverage reporting with Codecov integration +- Updated phpunit to version ^9.5 + +### Fixed +- Response parsing to handle multiple header sections (e.g., 100 Continue followed by 201 Created) +- Removed accidentally committed credentials (security fix) + +### Security +- **Major security enhancement:** Session tokens can now be encrypted at rest +- Protects against filesystem access, backup exposure, and disk forensics +- Uses authenticated encryption (AES-256-GCM) to detect tampering + +## [1.3.3] - 2017-03-31 + +### Fixed +- PHP 7 support in composer.json configuration + +## [1.3.2] - 2017-03-31 + +### Changed +- Updated API subdomain endpoints to new FamilySearch infrastructure + +## [1.3.1] - 2016-11-29 + +### Added +- Apache 2.0 license to composer.json metadata + +## [1.3.0] - 2016-11-29 + +### Added +- Optional integration with gedcomx-php library for object serialization +- `objects` configuration option to enable gedcomx-php serialization/deserialization +- Support for gedcomx-php objects in request bodies +- Automatic deserialization of responses into Atom Feed or FamilySearchPlatform objects + +### Changed +- Updated documentation with gedcomx-php usage examples + +## [1.2.0] - 2016-09-21 + +### Added +- Custom User-Agent support via `userAgent` configuration option +- SDK version number appended to default User-Agent string +- Pending modifications support via `pendingModifications` configuration option + +### Changed +- Default User-Agent format: `FS-PHP-Lite/{VERSION} curl/{VERSION} PHP/{VERSION}` + +## [1.1.0] - 2016-09-09 + +### Changed +- **BREAKING:** Replaced "sandbox" environment references with "integration" +- Updated environment configuration to use 'integration' instead of 'sandbox' +- Updated documentation and examples to reflect integration environment + +## [1.0.0] - 2016-07-29 + +Initial release of FamilySearch PHP Lite SDK. + +### Added +- Core SDK functionality for FamilySearch API integration +- OAuth 2.0 authentication flow (redirect and response handling) +- HTTP methods: GET, POST, HEAD, DELETE +- Session-based access token storage +- Automatic JSON parsing of API responses +- Response object with comprehensive metadata +- Throttling handling with automatic retry logic +- Query parameter support +- Custom header support +- Redirect following +- Request/response debugging capabilities +- Working example application +- PHPUnit test suite with Travis CI integration +- Basic documentation in README.md + +### Security +- Session-based token storage (tokens stored in `$_SESSION`) +- Support for manual token management via `accessToken` option + +## Version History Summary + +- **[Unreleased]** - PHP 7.4+ support, terminology updates, enhanced testing +- **[1.3.0]** - Session encryption, comprehensive security documentation (master branch) +- **1.3.3** - PHP 7 composer fix +- **1.3.2** - API subdomain updates +- **1.3.1** - License metadata +- **1.3.0** - gedcomx-php integration +- **1.2.0** - Custom user agent and pending modifications +- **1.1.0** - Integration environment terminology +- **1.0.0** - Initial release + +--- + +## Notes + +### Versioning Conflict + +There is a version numbering conflict in the repository: +- Git tag `1.3.0` was used for the gedcomx-php integration (2016) +- Code constant `VERSION = '1.3.0'` was updated for the encryption feature (2024) + +The master branch changes (encryption feature) should be released as **2.0.0** or **1.4.0** to avoid confusion. + +### Migration Guide + +#### From 1.3.x to 2.0.0 (Encryption Update) + +**No breaking changes** - The encryption feature is opt-in: + +```php +// Existing code continues to work unchanged +$fs = new FamilySearch([ + 'appKey' => $_ENV['FS_APP_KEY'], + 'environment' => 'production' +]); + +// Enable encryption (recommended for production) +$fs = new FamilySearch([ + 'appKey' => $_ENV['FS_APP_KEY'], + 'environment' => 'production', + 'sessionEncryption' => true, + 'sessionEncryptionKey' => $_ENV['FS_SESSION_ENCRYPTION_KEY'] +]); +``` + +Generate encryption key: +```bash +php -r "echo base64_encode(random_bytes(32));" +``` + +See [SECURITY.md](SECURITY.md) for comprehensive encryption setup guide. + +#### From 1.0.x/1.1.x to 1.2.0+ + +- Update environment references from `'sandbox'` to `'integration'` +- Update API endpoint references in documentation + +--- + +[Unreleased]: https://github.com/FamilySearch/fs-php-lite/compare/1.3.3...HEAD +[1.3.0]: https://github.com/FamilySearch/fs-php-lite/compare/1.3.3...master +[1.3.3]: https://github.com/FamilySearch/fs-php-lite/compare/1.3.2...1.3.3 +[1.3.2]: https://github.com/FamilySearch/fs-php-lite/compare/1.3.1...1.3.2 +[1.3.1]: https://github.com/FamilySearch/fs-php-lite/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/FamilySearch/fs-php-lite/compare/1.2.0...1.3.0 +[1.2.0]: https://github.com/FamilySearch/fs-php-lite/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/FamilySearch/fs-php-lite/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/FamilySearch/fs-php-lite/releases/tag/1.0.0 diff --git a/README.md b/README.md index ea3a199..cf0ecba 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [](https://packagist.org/packages/familysearch/fs-php-lite)  -[](https://github.com/FamilySearch/fs-php-lite) +[](https://github.com/FamilySearch/fs-php-lite) > **⚠️ Security Notice:** Access tokens are stored in plaintext by default. Enable encryption in production. See [Security Considerations](#security-considerations). @@ -13,8 +13,41 @@ considered bad practice when using the API. In most cases, FamilySearch does not consider URL changes as breaking changes. Read more about [dealing with change](https://familysearch.org/developers/docs/guides/evolution). -There is a sample app in the `/examples` directory that is deployed to -http://fs-php-lite-sdk.herokuapp.com/examples/. +There is a sample app in the `/examples` directory that demonstrates SDK usage. + +## Environments + +The SDK supports three FamilySearch environments: + +### Integration +Internal testing environment for FamilySearch developers and CI/CD pipelines. Not intended for external developer use. +- **Identity/OAuth**: `https://identint.familysearch.org` +- **Platform API**: `https://api-integ.familysearch.org` +- **Use case**: Internal FamilySearch testing + +### Beta +Pre-production environment for testing upcoming API features and validating application compatibility before changes reach production. +- **Identity/OAuth**: `https://identbeta.familysearch.org` +- **Platform API**: `https://apibeta.familysearch.org` +- **Use case**: External developer pre-release testing + +### Production +Live production environment with real FamilySearch user data. +- **Identity/OAuth**: `https://ident.familysearch.org` +- **Platform API**: `https://api.familysearch.org` +- **Use case**: Live production data + +**Example:** +```php +// Integration +$fs = new FamilySearch(['environment' => 'integration']); + +// Beta +$fs = new FamilySearch(['environment' => 'beta']); + +// Production +$fs = new FamilySearch(['environment' => 'production']); +``` ## Usage @@ -24,7 +57,7 @@ include_once('FamilySearch.php'); // Create the SDK instance $fs = new FamilySearch([ - 'environment' => 'production', + 'environment' => 'beta', // 'integration', 'beta', or 'production' 'appKey' => $_ENV['FS_APP_KEY'], // NEVER hardcode credentials - use environment variables 'redirectUri' => 'https://example.com/fs-redirect', @@ -290,9 +323,9 @@ for serialization from objects for requests and deserialization into objects for responses. ```php -$fs = new FamilySearch({ +$fs = new FamilySearch([ 'objects' => true -}); +]); $response = $fs->post('/platform/tree/persons', [ 'body' => new \Gedcomx\Extensions\FamilySearch\FamilySearchPlatform([ @@ -313,7 +346,7 @@ or later is required. ## Testing -The SDK includes comprehensive unit and integration tests with **79.89% code coverage**. +The SDK includes comprehensive unit and integration tests with **76.33% code coverage**. ### Quick Start @@ -327,7 +360,8 @@ composer test # Run only unit tests (fast, ~0.01s) composer test:unit -# Run only integration tests (with VCR, ~9s) +# Run integration tests against live FamilySearch integration API +# Requires credentials (see below) composer test:integration # Generate code coverage report @@ -336,31 +370,47 @@ composer test:coverage ### Test Suite Statistics -- **60 tests** with 123 assertions -- **79.89% line coverage**, 72.22% method coverage -- **52 unit tests** - Fast, no HTTP requests -- **11 integration tests** - Using recorded API responses (php-vcr) -- **2 skipped tests** - Due to VCR limitations (documented) +- **102 tests** with 232 assertions +- **76.33% line coverage**, 50.00% method coverage +- **74 unit tests** - Fast, no HTTP requests +- **28 integration tests** - Test against live FamilySearch integration API ### Test Structure ``` tests/ ├── Unit/ # 52 tests - SDK logic without HTTP -├── Integration/ # 11 tests - Full SDK with recorded HTTP -├── fixtures/ # VCR cassettes (HTTP recordings) +├── Integration/ # 11 tests - Full SDK against live API +├── fixtures/ # Test data (person.json) └── bootstrap.php # Test configuration ``` +### Integration Test Credentials + +Integration tests require FamilySearch integration environment credentials. Set these environment variables: + +```bash +export FAMILYSEARCH_USERNAME="your-integration-username" +export FAMILYSEARCH_PASSWORD="your-integration-password" +export FAMILYSEARCH_API_KEY="your-api-key" +export FAMILYSEARCH_REDIRECT_URI="http://example.com/redirect" # optional +``` + +**How to get credentials:** +1. Visit https://developers.familysearch.org/ +2. Create an account and register an application +3. Request integration environment access +4. Use your integration credentials for testing + ### Running Tests on Specific PHP Versions ```bash # Using Docker -docker run --rm -v $(pwd):/app -w /app php:8.0-cli composer test +docker run --rm -v $(pwd):/app -w /app php:7.4-cli composer test docker run --rm -v $(pwd):/app -w /app php:8.3-cli composer test # Using phpenv (if installed) -phpenv local 8.0 && composer test +phpenv local 7.4 && composer test phpenv local 8.3 && composer test ``` @@ -374,32 +424,14 @@ composer test:coverage open coverage/index.html ``` -### Re-recording VCR Cassettes - -VCR cassettes record API responses for fast, deterministic integration tests. - -```bash -# Set credentials (required for re-recording) -export FAMILYSEARCH_USERNAME="your-sandbox-username" -export FAMILYSEARCH_PASSWORD="your-sandbox-password" -export FAMILYSEARCH_API_KEY="your-api-key" - -# Delete old cassettes -rm tests/fixtures/test*.json - -# Re-run integration tests (records new cassettes) -composer test:integration -``` - -**Note:** Cassettes should be re-recorded quarterly or when API changes. - -See [TESTING.md](TESTING.md) for detailed testing documentation, and [RERECORD_VCR_GUIDE.md](RERECORD_VCR_GUIDE.md) for complete cassette re-recording instructions. +See [TESTING.md](TESTING.md) for detailed instructions to create testing for your own application. ## Requirements -- PHP 8.0 or higher +- PHP 7.4 or higher - ext-curl - ext-json +- Composer for dependency management ## Development @@ -414,24 +446,23 @@ See [TESTING.md](TESTING.md) for detailed testing documentation, and [RERECORD_V ### CI/CD Tests run automatically via GitHub Actions on: -- **PHP 8.0, 8.1, 8.2, and 8.3** (full matrix) +- **PHP 7.4, 8.0, 8.1, 8.2, 8.3, and 8.4** - **Every push** to master/main branches - **Every pull request** - **Code coverage** generated for PHP 8.3 - **Coverage reports** uploaded to Codecov #### CI Status -- ✅ All PHP versions passing (8.0-8.3) -- ✅ 60 tests, 123 assertions -- ✅ 79.89% code coverage -- ✅ No deprecation warnings +- ✅ All PHP versions passing (7.4-8.4) +- ✅ 102 tests, 232 assertions +- ✅ 76.33% code coverage (258/338 lines) See [.github/workflows/tests.yml](.github/workflows/tests.yml) for CI configuration. ### PHP Version Compatibility -**Minimum:** PHP 8.0 -**Tested:** PHP 8.0, 8.1, 8.2, 8.3, 8.5 +**Minimum:** PHP 7.4 +**Tested:** PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 **Recommended:** PHP 8.2+ for security updates -All tests pass on PHP 8.0-8.5 with zero deprecation warnings. \ No newline at end of file +All tests pass on PHP 7.4-8.4 with zero deprecation warnings. \ No newline at end of file diff --git a/RERECORD_VCR_GUIDE.md b/RERECORD_VCR_GUIDE.md deleted file mode 100644 index 9311f9c..0000000 --- a/RERECORD_VCR_GUIDE.md +++ /dev/null @@ -1,516 +0,0 @@ -# VCR Cassette Re-recording Guide - -## Overview - -This guide provides step-by-step instructions for re-recording VCR cassettes with fresh API responses from the FamilySearch integration environment. - -## Prerequisites - -### 1. FamilySearch Developer Account -You need credentials from the FamilySearch Developer Program: -- Username and password for sandbox environment -- API key (app key) from registered application - -**How to get credentials:** -1. Visit https://developers.familysearch.org/ -2. Create an account or sign in -3. Register a new application -4. Request sandbox access -5. If approved, note your API key and sandbox credentials - -### 2. Required Environment Variables - -Set these before re-recording: - -```bash -export FAMILYSEARCH_USERNAME="your-sandbox-username" -export FAMILYSEARCH_PASSWORD="your-sandbox-password" -export FAMILYSEARCH_API_KEY="your-app-key" -export FAMILYSEARCH_REDIRECT_URI="http://example.com/redirect" -``` - -**Verify credentials are set:** -```bash -env | grep FAMILYSEARCH -``` - -### 3. Dependencies Installed - -```bash -composer install -``` - -## Current Cassette Status - -### Active Cassettes (tests/fixtures/) -- ✅ `testAuthenticate.json` - Recent (June 2026) -- ✅ `testPost.json` - Recent (June 2026) -- ✅ `testGet.json` - Recent (June 2026) -- ✅ `testHead.json` - Recent (June 2026) -- ✅ `testDelete.json` - Recent (June 2026) -- ✅ `testUserAgent.json` - Recent (June 2026) -- ⚠️ `testRedirect.json` - Older (May 2026) -- ⚠️ `testPendingModification.json` - Older (May 2026) -- ⚠️ `person.json` - Older (May 2026) - -### Unused Cassettes (tests/fixtures/gedcomx/) -These cassettes are **NOT used** by any tests: -- `testAuthenticate.json` - From 2017 -- `testPost.json` - From 2017 -- `testGet.json` - From 2017 -- `testHead.json` - From 2017 -- `testDelete.json` - From 2017 -- `testRedirect.json` - From 2017 - -**Recommendation:** Delete gedcomx/ directory (not used) - -## Re-recording Methods - -### Method 1: Re-record All Cassettes (Recommended) - -**Best for:** Fresh start, consistency across all cassettes - -```bash -# 1. Set credentials -export FAMILYSEARCH_USERNAME="your-username" -export FAMILYSEARCH_PASSWORD="your-password" -export FAMILYSEARCH_API_KEY="your-api-key" - -# 2. Backup existing cassettes (optional) -mkdir -p tests/fixtures_backup -cp tests/fixtures/*.json tests/fixtures_backup/ - -# 3. Delete old cassettes -rm tests/fixtures/*.json - -# 4. Re-run integration tests (will record fresh cassettes) -composer test:integration - -# 5. Verify all tests pass -composer test:integration - -# 6. Check cassettes were created -ls -lh tests/fixtures/*.json - -# 7. Inspect a cassette to verify content -head -50 tests/fixtures/testAuthenticate.json -``` - -**Expected output:** -``` -PHPUnit 9.6.35 by Sebastian Bergmann and contributors. - -.....SS. 8 / 8 (100%) - -Time: 00:XX.XXX, Memory: 6.00 MB - -OK, but incomplete, skipped, or risky tests! -Tests: 8, Assertions: 36, Skipped: 2. -``` - -**New cassettes created:** -- ✅ testAuthenticate.json -- ✅ testPost.json -- ✅ testGet.json -- ✅ testHead.json -- ✅ testDelete.json -- ⏭️ testRedirect.json (test skipped, no cassette created) -- ⏭️ testPendingModification.json (test skipped, no cassette created) -- ✅ testUserAgent.json - -### Method 2: Re-record Specific Cassettes - -**Best for:** Updating individual cassettes without changing others - -```bash -# 1. Set credentials (as above) - -# 2. Delete specific cassette -rm tests/fixtures/testGet.json - -# 3. Run specific test to re-record -vendor/bin/phpunit --filter testGet tests/Integration/FamilySearchIntegrationTest.php - -# 4. Verify test passes -vendor/bin/phpunit --filter testGet tests/Integration/FamilySearchIntegrationTest.php -``` - -**Available test filters:** -- `testAuthenticate` - OAuth authentication -- `testPost` - Create person -- `testGet` - Read person -- `testHead` - HEAD request -- `testDelete` - Delete person -- `testUserAgent` - User agent test - -### Method 3: Re-record Stale Cassettes Only - -**Best for:** Updating only older cassettes - -```bash -# 1. Set credentials - -# 2. Delete only stale cassettes -rm tests/fixtures/testRedirect.json -rm tests/fixtures/testPendingModification.json -rm tests/fixtures/person.json - -# 3. Re-run integration tests -composer test:integration - -# Note: testRedirect and testPendingModification are skipped, -# so those cassettes won't be re-created -``` - -## Handling Skipped Tests - -### testRedirect (Always Skipped) -**Reason:** VCR doesn't properly replay redirect responses - -**Re-recording:** Will NOT create cassette (test is marked as skipped) - -**Manual testing required:** -```bash -# Test redirects against live API (without VCR) -# Edit test to temporarily remove markTestSkipped() call -# Run against live API -# Verify redirect behavior works -# Restore markTestSkipped() call -``` - -### testPendingModification (Always Skipped) -**Reason:** Dynamic person IDs don't match pre-recorded cassette URLs - -**Re-recording:** Will NOT create cassette (test is marked as skipped) - -**Manual testing required:** -```bash -# Similar to testRedirect - test manually without VCR -``` - -## Verifying Re-recorded Cassettes - -### 1. Check Cassette Content - -**Verify User-Agent is current:** -```bash -jq -r '.[0].request.headers["User-Agent"]' tests/fixtures/testAuthenticate.json -``` - -**Expected:** -``` -FS-PHP-Lite/1.3.0 curl/8.x PHP/8.x -``` - -**NOT (old):** -``` -FS-PHP-Lite/1.2.0 curl/7.35.0 PHP/5.5.9-1ubuntu4.17 -``` - -### 2. Check Timestamps - -**Verify recent modification dates:** -```bash -ls -lh tests/fixtures/*.json -``` - -All cassettes should show today's date. - -### 3. Run Tests Multiple Times - -**First run (using fresh cassettes):** -```bash -composer test:integration -``` - -**Second run (should be faster, using cassettes):** -```bash -composer test:integration -``` - -Second run should be very fast (no network calls) and produce identical results. - -### 4. Verify Response Structure - -**Check a cassette has expected structure:** -```bash -jq '.[0] | keys' tests/fixtures/testAuthenticate.json -``` - -**Expected:** -```json -[ - "request", - "response" -] -``` - -### 5. Check Request/Response Completeness - -**Example for testGet:** -```bash -jq 'length' tests/fixtures/testGet.json -# Should be 3 (OAuth + Create Person + Get Person) - -jq '.[].request.url' tests/fixtures/testGet.json -# Should show 3 URLs -``` - -### 6. Verify Integration Tests Pass - -```bash -composer test:integration - -# All tests should pass (2 skipped as expected) -# Tests: 8, Assertions: 36, Skipped: 2 -``` - -### 7. Verify Unit Tests Still Pass - -```bash -composer test:unit - -# All unit tests should pass -# Tests: 24, Assertions: 37 -``` - -### 8. Verify Coverage Maintained - -```bash -composer test:coverage - -# Coverage should be maintained: 77.72%+ -``` - -## Troubleshooting - -### Issue: "No credentials found" - -**Problem:** Integration tests can't authenticate - -**Solution:** -```bash -# Verify environment variables are set -env | grep FAMILYSEARCH - -# Or create credentials file -cp tests/Integration/SandboxCredentials.example.php \ - tests/Integration/SandboxCredentials.php -# Edit with your credentials -``` - -### Issue: "404 Not Found" when re-recording - -**Problem:** API endpoints have changed or person doesn't exist - -**Solution:** -- Delete ALL cassettes and start fresh -- Person IDs in old cassettes don't exist in sandbox anymore -- Re-recording creates fresh persons - -### Issue: "429 Too Many Requests" - -**Problem:** Rate limiting from FamilySearch API - -**Solution:** -```bash -# Wait a few minutes -sleep 300 - -# Re-run -composer test:integration - -# Or re-record one test at a time with delays -vendor/bin/phpunit --filter testAuthenticate tests/Integration/ -sleep 60 -vendor/bin/phpunit --filter testPost tests/Integration/ -sleep 60 -# etc. -``` - -### Issue: Cassette not created after test - -**Problem:** Test is skipped or failed before recording - -**Solution:** -```bash -# Check if test is marked as skipped -grep -A5 "testRedirect" tests/Integration/FamilySearchIntegrationTest.php - -# Look for markTestSkipped() call -``` - -### Issue: "VCR is turned off" - -**Problem:** VCR configuration issue - -**Solution:** -- Check `tests/bootstrap.php` has VCR configuration -- Ensure `php-vcr/php-vcr` is installed: `composer show php-vcr/php-vcr` - -### Issue: Tests pass with cassettes but fail with live API - -**Problem:** API behavior has changed - -**Solution:** -1. Review API response differences -2. Update test expectations if needed -3. Update SDK if API contract changed -4. Document breaking changes - -### Issue: Sensitive data in cassettes - -**Problem:** Access tokens or credentials in cassette files - -**Solution:** -```bash -# Check for sensitive data -grep -i "access_token\|password" tests/fixtures/*.json - -# Access tokens are OK (temporary, sandbox) -# Passwords should NOT appear (already URL-encoded in body) - -# If needed, sanitize: -sed -i.bak 's/USYS[A-Z0-9_]*/REDACTED_TOKEN/g' tests/fixtures/*.json -``` - -## Cassette Maintenance Schedule - -### When to Re-record - -**Required:** -- When API contract changes -- When tests fail unexpectedly with cassettes -- When cassettes contain ancient data (>6 months old) - -**Recommended:** -- Quarterly (every 3 months) -- Before major releases -- After SDK updates that change API usage - -**Optional:** -- Weekly (for active development) -- When adding new tests - -### Quick Check - -```bash -# Check cassette age -ls -lht tests/fixtures/*.json | head -5 - -# If oldest is >3 months, consider re-recording -``` - -## Clean-up Tasks - -### Remove Unused Fixtures - -```bash -# gedcomx/ fixtures are NOT used by any tests -rm -rf tests/fixtures/gedcomx/ - -# Verify tests still pass -composer test:integration -``` - -### Verify No Duplicate Cassettes - -```bash -# Check for duplicates -find tests/fixtures -name "*.json" | sort - -# Should only see each test name once (no duplicates) -``` - -## Security Best Practices - -### 1. Never Commit Credentials - -```bash -# Verify .gitignore has: -cat .gitignore | grep -i credential -# Should show: tests/Integration/SandboxCredentials.php -``` - -### 2. Use Environment Variables in CI - -```yaml -# .github/workflows/tests.yml should NOT have credentials -# CI runs with cassettes only (no live API calls) -``` - -### 3. Sanitize Tokens (Optional) - -```bash -# If publishing cassettes publicly, consider sanitizing -# Replace real tokens with placeholders -# Note: Cassettes won't work for re-recording after sanitization -``` - -### 4. Use Sandbox Environment Only - -- Never use production credentials -- Never record cassettes from production API -- Sandbox data is safe to commit - -## Post-Recording Checklist - -After re-recording cassettes: - -- [ ] All integration tests pass -- [ ] Cassettes have current timestamps -- [ ] User-Agent strings are current (PHP 8.x, curl 8.x) -- [ ] No sensitive data in cassettes (or sanitized) -- [ ] gedcomx/ directory removed (unused) -- [ ] Tests pass WITHOUT credentials (using cassettes only) -- [ ] Coverage maintained at 77.72%+ -- [ ] Documentation updated if API changes detected -- [ ] Cassettes committed to git - -## Commit Message Template - -``` -chore: re-record VCR cassettes with current API responses - -- Updated all cassettes to reflect current FamilySearch API -- User-Agent now shows PHP 8.x and curl 8.x -- Removed unused gedcomx/ fixtures -- All tests passing (8 tests, 2 skipped) -- Coverage maintained at 77.72% -``` - -## Quick Reference Commands - -```bash -# Check cassette age -ls -lht tests/fixtures/*.json - -# Re-record all -rm tests/fixtures/*.json && composer test:integration - -# Re-record one -rm tests/fixtures/testGet.json && vendor/bin/phpunit --filter testGet tests/Integration/ - -# Verify cassettes -jq '.[0].request.headers["User-Agent"]' tests/fixtures/*.json - -# Test without credentials -unset FAMILYSEARCH_USERNAME FAMILYSEARCH_PASSWORD FAMILYSEARCH_API_KEY -composer test:integration - -# Check for sensitive data -grep -i "password\|secret" tests/fixtures/*.json -``` - -## Additional Resources - -- **FamilySearch API:** https://developers.familysearch.org/ -- **Sandbox Guide:** https://developers.familysearch.org/main/reference/api-reference-guide -- **Test Documentation:** See `TESTING.md` in repository - ---- - -**Guide Version:** 1.0 -**Last Updated:** July 26, 2026 -**VCR Version:** 1.11.2 -**PHPUnit Version:** 9.6.35 diff --git a/SECURITY.md b/SECURITY.md index a9ea4cb..20dec19 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,15 @@ # Security Policy +## Supported Versions + +- **Minimum:** PHP 7.4 +- **Recommended:** PHP 8.2+ for active security updates and performance improvements +- **Tested:** PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 + +**Note:** PHP versions below 8.1 have reached end-of-life and no longer receive security updates from the PHP team. While this SDK supports PHP 7.4+, we strongly recommend using PHP 8.2 or later in production environments. + +--- + ## Security Overview ### What the SDK Provides @@ -660,7 +670,8 @@ unset FS_SESSION_ENCRYPTION_KEY_OLD If you discover a security vulnerability in this SDK, please report it responsibly: **DO:** -- ✅ Email security issues privately to: [justincyork@gmail.com](mailto:justincyork@gmail.com) +- ✅ **SDK Issues:** Email security issues privately to [devsupport@familysearch.org](mailto:devsupport@familysearch.org) +- ✅ **FamilySearch Platform Issues:** Report through [FamilySearch Developer Support](https://developers.familysearch.org/) - ✅ Provide detailed steps to reproduce - ✅ Include proof-of-concept code (if applicable) - ✅ Give us reasonable time to fix (90 days) @@ -736,7 +747,7 @@ We aim to: - [OWASP PHP Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/PHP_Configuration_Cheat_Sheet.html) - [PHP Session Security](https://www.php.net/manual/en/session.security.php) - [NIST Cryptographic Standards](https://csrc.nist.gov/publications/detail/sp/800-175b/rev-1/final) -- [FamilySearch API Documentation](https://www.familysearch.org/developers/docs/api/) +- [FamilySearch API Documentation](https://developers.familysearch.org/docs/api/) ### PHP Security Configuration diff --git a/TESTING.md b/TESTING.md index 4fbf37d..f9be927 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,341 +1,91 @@ -# Testing Guide for fs-php-lite +# Testing Your Application with fs-php-lite -This guide explains how to run and write tests for the FamilySearch PHP Lite SDK. +This guide helps you test your own applications that use the FamilySearch PHP Lite SDK. -## Testing Strategy +## Unit Testing Your Code -The SDK uses a multi-layered testing approach: - -1. **Unit Tests** - Test SDK methods in isolation without making HTTP requests -2. **Integration Tests** - Test SDK against recorded FamilySearch API responses using php-vcr -3. **Example Applications** - Working demos that serve as smoke tests - -## Prerequisites - -- PHP 8.1, 8.2, or 8.3 -- Composer -- Xdebug (for code coverage) - -## Installation - -Install development dependencies: - -```bash -composer install -``` - -## Running Tests - -### Run All Tests -```bash -composer test -``` - -### Run Unit Tests Only -```bash -composer test:unit -``` - -### Run Integration Tests Only -```bash -composer test:integration -``` - -### Run with Code Coverage -```bash -composer test:coverage -``` - -This generates an HTML coverage report in the `coverage/` directory. - -### Run Specific Test File -```bash -vendor/bin/phpunit tests/Unit/FamilySearchConfigTest.php -``` - -### Run Specific Test Method -```bash -vendor/bin/phpunit --filter testConstructorWithAccessToken -``` - -## Test Structure - -``` -tests/ -├── bootstrap.php # Test bootstrapping and VCR configuration -├── Unit/ # Unit tests (no HTTP requests) -│ ├── FamilySearchConfigTest.php -│ └── FamilySearchHttpMethodsTest.php -├── Integration/ # Integration tests (with VCR recordings) -│ ├── ApiTestCase.php -│ ├── SandboxCredentials.php -│ └── FamilySearchIntegrationTest.php -└── fixtures/ # VCR cassettes and test data - ├── testAuthenticate.json - ├── testGet.json - └── person.json -``` - -## Writing Tests - -### Unit Tests - -Unit tests should test SDK logic without making actual HTTP requests: +When writing unit tests for your application code that uses the SDK, you can mock the `FamilySearch` class to avoid making real HTTP requests: ```php - 'test-token']); + // Mock the SDK + $mockFS = $this->createMock(FamilySearch::class); + + // Set up expected behavior + $mockResponse = (object)[ + 'statusCode' => 200, + 'data' => ['persons' => [/* ... */]] + ]; + $mockFS->method('get')->willReturn($mockResponse); + + // Test your code that uses the SDK + $myService = new MyService($mockFS); + $result = $myService->doSomething(); - $this->assertEquals('test-token', $fs->getAccessToken()); + $this->assertNotEmpty($result); } } ``` -### Integration Tests +## Integration Testing Against Integration Environment -Integration tests use php-vcr to record and replay HTTP interactions: +To test your application against the live FamilySearch Integration environment, initialize the SDK with an access token obtained through the standard OAuth2 authorization flow: ```php -client->get('/platform/users/current'); + $this->fs = new FamilySearch([ + 'environment' => 'integration', + 'appKey' => $_ENV['FAMILYSEARCH_API_KEY'], + 'redirectUri' => $_ENV['FAMILYSEARCH_REDIRECT_URI'], + // Use a pre-obtained access token for testing + 'accessToken' => $_ENV['FAMILYSEARCH_ACCESS_TOKEN'] + ]); + } + + public function testGetCurrentUser(): void + { + $response = $this->fs->get('/platform/users/current'); - $this->assertResponseOK($response); - - VCR::eject(); - VCR::turnOff(); + $this->assertEquals(200, $response->statusCode); + $this->assertArrayHasKey('users', $response->data); } } ``` -The first time this test runs, it makes a real API call and records the response in `tests/fixtures/myTest.json`. Subsequent runs replay the recorded response. +**Note:** Integration tests require a valid access token. Since FamilySearch uses OAuth2 authorization code flow (which requires browser interaction), you'll need to: +1. Manually complete the OAuth flow once to obtain an access token +2. Store the token securely (e.g., in an environment variable) +3. Use that token in your automated tests +4. Refresh or regenerate the token when it expires -## Code Coverage Target +## Getting Integration Environment Access -The SDK aims for **70-80% code coverage** for core functionality: +To test against the FamilySearch Integration environment: -- `src/FamilySearch.php` - Target: 75%+ +1. Visit https://developers.familysearch.org/ +2. Create an account and register your application +3. Your app key is automatically enabled for Integration environment access +4. Obtain an access token: + - Visit https://integration.familysearch.org/platform/ (or https://beta.familysearch.org/platform/ for Beta) to get a token without completing the full OAuth2 flow + - Complete the OAuth2 authorization flow in your application +5. Store your credentials in environment variables: -To view coverage: ```bash -composer test:coverage -open coverage/index.html -``` - -## Continuous Integration - -Tests run automatically on every push and pull request via GitHub Actions. - -The CI pipeline: -- Tests against PHP 8.1, 8.2, and 8.3 -- Runs both unit and integration tests -- Generates code coverage reports (PHP 8.3 only) -- Uploads coverage to Codecov - -See [.github/workflows/tests.yml](.github/workflows/tests.yml) for configuration. - -## php-vcr (HTTP Recording) - -Integration tests use [php-vcr](https://github.com/php-vcr/php-vcr) to record and replay HTTP interactions. - -### How It Works - -1. First test run: Makes real HTTP request, saves response to cassette file -2. Subsequent runs: Replays response from cassette file (no network call) - -### Cassette Files - -Located in `tests/fixtures/`, these JSON files contain: -- Request details (method, URL, headers, body) -- Response details (status, headers, body) - -### Known VCR Limitations - -**Header Extraction Issues**: VCR cassettes contain the full HTTP response headers (including `X-ENTITY-ID`), but when VCR replays responses, some headers may not be properly accessible to the SDK. This is a known limitation of how VCR intercepts curl_exec() calls. Tests that depend on response headers may fail when run with VCR cassettes but pass against the live API. - -**Redirect Handling**: The SDK manually follows HTTP redirects to work around curl_exec() limitations. VCR's interception interferes with this redirect handling, causing redirect tests to fail during playback. The `testRedirect` test is intentionally skipped for this reason, and redirect functionality is verified through manual testing. - -**Dynamic Person IDs**: The `testPendingModification` test is skipped because VCR does not reliably replay workflows involving dynamically created resources. Each live API request creates a new person with a unique ID that doesn't match pre-recorded cassette URLs, causing VCR to make unintended live requests that result in 404 errors. This is a VCR infrastructure limitation, not an SDK defect. The pending modifications functionality (X-FS-Feature-Tag header) is working correctly and can be verified through manual testing against the live API. - -**Workarounds**: -- For development and CI, unit tests provide deterministic, fast validation without these issues -- Integration tests with VCR validate request/response structure and JSON parsing -- Manual testing against the live API (see below) validates full end-to-end behavior including headers, redirects, and dynamic workflows - -### Re-recording Cassettes - -To update cassettes with fresh API responses: - -```bash -# Delete old cassettes -rm tests/fixtures/*.json - -# Re-run tests to record new cassettes -composer test:integration -``` - -### VCR Configuration - -See `tests/bootstrap.php` for VCR configuration: -- Request matching rules -- Cassette storage location -- Library hooks - -## Common Issues - -### Test Failures - -**"Class 'FamilySearch' not found"** -- Run `composer install` to generate autoload files -- Verify `vendor/autoload.php` exists - -**"VCR cassette not found"** -- Ensure cassette file exists in `tests/fixtures/` -- Check the `@vcr` annotation matches the filename - -**"PHP Fatal error: Class 'PHPUnit\Framework\TestCase' not found"** -- Ensure you're using PHPUnit 10+: `composer require --dev phpunit/phpunit:^10.5` - -### Code Coverage - -**"No code coverage driver available"** -- Install Xdebug: `pecl install xdebug` -- Or use PCOV: `pecl install pcov` -- Enable extension in php.ini - -**Coverage report is empty** -- Ensure Xdebug is enabled: `php -v` should show "with Xdebug" -- Run with: `XDEBUG_MODE=coverage composer test:coverage` - -## Credentials for Integration Tests - -### Default: VCR Cassettes (No Credentials Needed) - -Integration tests use pre-recorded VCR cassettes by default. No credentials are required for normal testing: - -```bash -composer test:integration # Uses recorded API responses -``` - -### Optional: Testing Against Live API - -To test against the live FamilySearch sandbox API, you need credentials obtained through the [FamilySearch Developer Program](https://www.familysearch.org/developers/). - -**Important**: Credentials are NOT stored in this repository and must be provided externally. - -#### Option 1: Environment Variables (Recommended) - -Set environment variables before running tests: - -```bash -export FAMILYSEARCH_USERNAME="your-username" -export FAMILYSEARCH_PASSWORD="your-password" export FAMILYSEARCH_API_KEY="your-api-key" -export FAMILYSEARCH_REDIRECT_URI="http://example.com/redirect" # optional - -composer test:integration -``` - -#### Option 2: Local Configuration File - -Copy the example file and fill in your credentials: - -```bash -cp tests/Integration/SandboxCredentials.example.php tests/Integration/SandboxCredentials.php -# Edit SandboxCredentials.php with your credentials -``` - -**Note**: `SandboxCredentials.php` is git-ignored and will never be committed. - -### CI/CD Behavior - -GitHub Actions CI runs integration tests using **only** pre-recorded VCR cassettes. No live credentials are used in CI to ensure: -- Tests are fast and deterministic -- No risk of rate limiting -- No secrets management required -- Tests work even if the sandbox API is unavailable - -### Re-recording Cassettes - -To update cassettes with fresh API responses (requires credentials): - -```bash -# Set credentials via environment variables -export FAMILYSEARCH_USERNAME="your-username" -export FAMILYSEARCH_PASSWORD="your-password" -export FAMILYSEARCH_API_KEY="your-api-key" - -# Delete old cassettes -rm tests/fixtures/*.json - -# Re-run tests to record new responses -composer test:integration +export FAMILYSEARCH_REDIRECT_URI="http://localhost/callback" +export FAMILYSEARCH_ACCESS_TOKEN="your-access-token" ``` -**Note**: Live API testing can be affected by rate limiting and requires valid sandbox credentials. - -## Best Practices - -1. **Write unit tests first** - They're fast and don't require network access -2. **Use descriptive test names** - `testGetPersonReturnsValidResponse` not `testGet` -3. **One assertion per test** - Makes failures easier to diagnose -4. **Keep cassettes up to date** - Re-record when API changes -5. **Never commit credentials** - Always use environment variables or git-ignored files. Credentials are NOT stored in this repository. -6. **Test edge cases** - Error conditions, empty responses, malformed data -7. **Use VCR cassettes in CI** - Integration tests should run on recorded responses, not live API calls - -## PHP Version Testing - -To test against specific PHP versions locally using Docker: - -```bash -# PHP 8.1 -docker run --rm -v $(pwd):/app -w /app php:8.1-cli composer test - -# PHP 8.2 -docker run --rm -v $(pwd):/app -w /app php:8.2-cli composer test - -# PHP 8.3 -docker run --rm -v $(pwd):/app -w /app php:8.3-cli composer test -``` - -## Contributing - -When submitting pull requests: - -1. Ensure all tests pass: `composer test` -2. Add tests for new functionality -3. Maintain or improve code coverage -4. Update cassettes if API interactions changed -5. Run tests against all supported PHP versions - -## Resources +**Integration Environment URL:** https://integration.familysearch.org/ -- [PHPUnit Documentation](https://phpunit.de/documentation.html) -- [php-vcr Documentation](https://github.com/php-vcr/php-vcr) -- [FamilySearch API Documentation](https://www.familysearch.org/developers/docs/api) -- [GitHub Actions Documentation](https://docs.github.com/en/actions) +**Important:** Never commit credentials or access tokens to version control. Use environment variables or a git-ignored configuration file. diff --git a/composer.json b/composer.json index 21eb04d..03fe148 100644 --- a/composer.json +++ b/composer.json @@ -11,15 +11,13 @@ ], "license": "Apache-2.0", "require": { - "php": ">=8.0", + "php": ">=7.4", "ext-curl": "*", "ext-json": "*" }, "require-dev": { "gedcomx/gedcomx-php": "^3.1.2", - "php-vcr/php-vcr": "^1.6", - "phpunit/phpunit": "^9.5", - "php-vcr/phpunit-testlistener-vcr": "^3.0" + "phpunit/phpunit": "^9.5" }, "autoload": { "classmap": ["src/"] @@ -37,7 +35,7 @@ }, "config": { "platform": { - "php": "8.0.30" + "php": "7.4.0" } } } diff --git a/composer.lock b/composer.lock index 1eb19ed..db2ef77 100644 --- a/composer.lock +++ b/composer.lock @@ -4,76 +4,9 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "28e07a29c49adebd1ccf5439630bbf82", + "content-hash": "e3e0a28ee4cd1baf74a338e2316b5440", "packages": [], "packages-dev": [ - { - "name": "beberlei/assert", - "version": "v3.3.4", - "source": { - "type": "git", - "url": "https://github.com/beberlei/assert.git", - "reference": "f193f4613c7d7fbcee2c05e4daff4061d49c040e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/f193f4613c7d7fbcee2c05e4daff4061d49c040e", - "reference": "f193f4613c7d7fbcee2c05e4daff4061d49c040e", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "phpstan/phpstan": "*", - "phpunit/phpunit": ">=6.0.0", - "yoast/phpunit-polyfills": "^0.1.0" - }, - "suggest": { - "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" - }, - "type": "library", - "autoload": { - "files": [ - "lib/Assert/functions.php" - ], - "psr-4": { - "Assert\\": "lib/Assert" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de", - "role": "Lead Developer" - }, - { - "name": "Richard Quadling", - "email": "rquadling@gmail.com", - "role": "Collaborator" - } - ], - "description": "Thin assertion library for input validation in business models.", - "keywords": [ - "assert", - "assertion", - "validation" - ], - "support": { - "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.4" - }, - "time": "2026-06-10T19:47:05+00:00" - }, { "name": "doctrine/instantiator", "version": "1.5.0", @@ -196,20 +129,20 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -244,15 +177,15 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "nikic/php-parser", @@ -429,114 +362,6 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "php-vcr/php-vcr", - "version": "1.11.2", - "source": { - "type": "git", - "url": "https://github.com/php-vcr/php-vcr.git", - "reference": "f255a2462607fc9ecd8a98c18869078f40ad103b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-vcr/php-vcr/zipball/f255a2462607fc9ecd8a98c18869078f40ad103b", - "reference": "f255a2462607fc9ecd8a98c18869078f40ad103b", - "shasum": "" - }, - "require": { - "beberlei/assert": "^3.2.5", - "ext-curl": "*", - "php": "^8,<8.2|>=8.2.9,<8.6", - "symfony/event-dispatcher": "^4|^5|^6|^7", - "symfony/event-dispatcher-contracts": "^1|^2|^3", - "symfony/yaml": "^3|^4|^5|^6|^7" - }, - "require-dev": { - "editorconfig-checker/editorconfig-checker": "^10.3", - "ext-soap": "*", - "friendsofphp/php-cs-fixer": "^3.0", - "guzzlehttp/guzzle": "^7", - "mikey179/vfsstream": "^1.6.10", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1", - "phpstan/phpstan-beberlei-assert": "^1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^9.5.10|^10.5|^11.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "thecodingmachine/phpstan-strict-rules": "^1" - }, - "type": "library", - "autoload": { - "psr-4": { - "VCR\\": "src/VCR/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Adrian Philipp", - "email": "mail@adrian-philipp.com" - }, - { - "name": "Daniel Hürtgen", - "email": "daniel@higidi.com", - "role": "Maintainer" - } - ], - "description": "Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.", - "support": { - "issues": "https://github.com/php-vcr/php-vcr/issues", - "source": "https://github.com/php-vcr/php-vcr/tree/1.11.2" - }, - "time": "2026-07-18T20:32:27+00:00" - }, - { - "name": "php-vcr/phpunit-testlistener-vcr", - "version": "3.3.0", - "source": { - "type": "git", - "url": "https://github.com/php-vcr/phpunit-testlistener-vcr.git", - "reference": "e57a97f0c3942000350d03033d21e86ef48ac718" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-vcr/phpunit-testlistener-vcr/zipball/e57a97f0c3942000350d03033d21e86ef48ac718", - "reference": "e57a97f0c3942000350d03033d21e86ef48ac718", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0", - "php-vcr/php-vcr": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^7.0|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "VCR\\PHPUnit\\TestListener\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Adrian Philipp", - "email": "mail@adrian-philipp.com" - } - ], - "description": "Integrates PHPUnit with PHP-VCR.", - "support": { - "issues": "https://github.com/php-vcr/phpunit-testlistener-vcr/issues", - "source": "https://github.com/php-vcr/phpunit-testlistener-vcr/tree/3.3.0" - }, - "time": "2024-10-29T19:16:02+00:00" - }, { "name": "phpunit/php-code-coverage", "version": "9.2.32", @@ -858,16 +683,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.35", + "version": "9.6.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b" + "reference": "abab27ed286d3e1246fbbfe6b56bfd732d945ec9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0edba2f3a0c48df3553cb9b640810b30df60302b", - "reference": "0edba2f3a0c48df3553cb9b640810b30df60302b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/abab27ed286d3e1246fbbfe6b56bfd732d945ec9", + "reference": "abab27ed286d3e1246fbbfe6b56bfd732d945ec9", "shasum": "" }, "require": { @@ -892,7 +717,7 @@ "sebastian/comparator": "^4.0.10", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.8", + "sebastian/exporter": "^4.0.9", "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", @@ -941,7 +766,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.35" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.36" }, "funding": [ { @@ -949,57 +774,7 @@ "type": "other" } ], - "time": "2026-07-06T14:48:07+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" + "time": "2026-08-11T06:25:15+00:00" }, { "name": "sebastian/cli-parser", @@ -1442,16 +1217,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.8", + "version": "4.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" + "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", - "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/4352c1a3df741a7ba9e61af6fed51d1fee41cbf7", + "reference": "4352c1a3df741a7ba9e61af6fed51d1fee41cbf7", "shasum": "" }, "require": { @@ -1507,7 +1282,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.9" }, "funding": [ { @@ -1527,7 +1302,7 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:03:27+00:00" + "time": "2026-08-11T04:55:59+00:00" }, { "name": "sebastian/global-state", @@ -1776,16 +1551,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.6", + "version": "4.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" + "reference": "c85be6922b7fd365942b986b9a50397d65407611" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", - "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c85be6922b7fd365942b986b9a50397d65407611", + "reference": "c85be6922b7fd365942b986b9a50397d65407611", "shasum": "" }, "require": { @@ -1827,7 +1602,7 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.7" }, "funding": [ { @@ -1847,7 +1622,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T06:57:39+00:00" + "time": "2026-08-11T05:25:24+00:00" }, { "name": "sebastian/resource-operations", @@ -2012,397 +1787,6 @@ ], "time": "2020-09-28T06:39:44+00:00" }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "" - }, - "require": { - "php": ">=8.0.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v6.0.19", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "2eaf8e63bc5b8cefabd4a800157f0d0c094f677a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2eaf8e63bc5b8cefabd4a800157f0d0c094f677a", - "reference": "2eaf8e63bc5b8cefabd4a800157f0d0c094f677a", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "symfony/event-dispatcher-contracts": "^2|^3" - }, - "conflict": { - "symfony/dependency-injection": "<5.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^5.4|^6.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.19" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-01T08:36:10+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", - "shasum": "" - }, - "require": { - "php": ">=8.0.2", - "psr/event-dispatcher": "^1" - }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:55:41+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-04-10T16:19:22+00:00" - }, - { - "name": "symfony/yaml", - "version": "v5.4.53", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "ae0bbb46f77ff56591d0a0259c7f458f4b3e1f77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ae0bbb46f77ff56591d0a0259c7f458f4b3e1f77", - "reference": "ae0bbb46f77ff56591d0a0259c7f458f4b3e1f77", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<5.3" - }, - "require-dev": { - "symfony/console": "^5.3|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.53" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-20T17:13:41+00:00" - }, { "name": "theseer/tokenizer", "version": "1.3.1", @@ -2457,7 +1841,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": {}, - "prefer-stable": true, + "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.0", diff --git a/examples/.env.example b/examples/.env.example index 7d5c988..4cc0327 100644 --- a/examples/.env.example +++ b/examples/.env.example @@ -6,7 +6,7 @@ # FamilySearch API Key # --------------------- -# Get your developer app key from: https://www.familysearch.org/developers/ +# Get your developer app key from: https://developers.familysearch.org/ # Register your application and copy the app key here FS_APP_KEY=your_familysearch_app_key_here diff --git a/examples/README.md b/examples/README.md index ee085d0..4ae4fca 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,9 +4,9 @@ This directory contains working examples demonstrating how to use the fs-php-lit ## Prerequisites -- PHP 8.1 or higher +- PHP 7.4 or higher - Composer -- FamilySearch developer account and API key ([register here](https://www.familysearch.org/developers/)) +- FamilySearch developer account and API key ([register here](https://developers.familysearch.org/)) ## Setup @@ -18,12 +18,17 @@ composer install 2. Configure your API credentials in `_includes.php` or set environment variables: ```php $config = [ - 'environment' => 'beta', // or 'production' + 'environment' => 'integration', // 'integration', 'beta', or 'production' 'appKey' => 'YOUR_APP_KEY', 'redirectUri' => 'http://localhost:8080/examples/oauthResponse.php' ]; ``` +**Environment Options:** +- `integration`: Internal FamilySearch testing +- `beta`: External developer pre-release testing +- `production`: Live production data + 3. Start the built-in PHP server: ```bash php -S localhost:8080 @@ -90,7 +95,7 @@ php examples/readPerson.php - Examples use the FamilySearch Integration environment by default - Access tokens are stored in PHP sessions -- For production use, implement proper token storage and security +- For production use, enable encryption and implement proper security (see [SECURITY.md](../SECURITY.md)) - See the main [README.md](../README.md) for full SDK documentation ## Troubleshooting diff --git a/examples/_includes.php b/examples/_includes.php index f472529..2d06505 100644 --- a/examples/_includes.php +++ b/examples/_includes.php @@ -31,8 +31,8 @@
.env.example to .env in the examples directoryFS_APP_KEY in your .env file.env is in your .gitignore (NEVER commit credentials!)', print_r($var, true), ''; } /** * Calculate the apps protocol and domain. This allows us to run the app both * locally and in Heroku without having to modify the redirect URI. - * + * * @return string */ -function calculateBaseUrl(){ +function calculateBaseUrl(): string +{ return ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?: $_SERVER['REQUEST_SCHEME']) . '://' . $_SERVER['HTTP_HOST']; } \ No newline at end of file diff --git a/src/FamilySearch.php b/src/FamilySearch.php index f4aa1bb..408169b 100644 --- a/src/FamilySearch.php +++ b/src/FamilySearch.php @@ -856,7 +856,7 @@ private function identHost() case 'beta': return 'https://identbeta.familysearch.org'; default: - return 'https://integration.familysearch.org'; + return 'https://identint.familysearch.org'; } } @@ -871,7 +871,7 @@ private function platformHost() case 'production': return 'https://api.familysearch.org'; case 'beta': - return 'https://beta.familysearch.org'; + return 'https://apibeta.familysearch.org'; default: return 'https://api-integ.familysearch.org'; } diff --git a/tests/Integration/ApiTestCase.php b/tests/Integration/ApiTestCase.php index fe1e25d..06ddb05 100644 --- a/tests/Integration/ApiTestCase.php +++ b/tests/Integration/ApiTestCase.php @@ -13,7 +13,7 @@ abstract class ApiTestCase extends TestCase private static ?array $personData = null; /** - * Get credentials from environment or SandboxCredentials class + * Get credentials from environment or IntegrationCredentials class */ protected function getCredentials(): array { @@ -23,12 +23,12 @@ protected function getCredentials(): array $apiKey = getenv('FAMILYSEARCH_API_KEY'); $redirectUri = getenv('FAMILYSEARCH_REDIRECT_URI'); - // Fall back to SandboxCredentials if it exists and has values - if (empty($apiKey) && class_exists('FamilySearch\Tests\Integration\SandboxCredentials')) { - $username = $username ?: SandboxCredentials::USERNAME; - $password = $password ?: SandboxCredentials::PASSWORD; - $apiKey = $apiKey ?: SandboxCredentials::API_KEY; - $redirectUri = $redirectUri ?: SandboxCredentials::REDIRECT_URI; + // Fall back to IntegrationCredentials if it exists and has values + if (empty($apiKey) && class_exists('FamilySearch\Tests\Integration\IntegrationCredentials')) { + $username = $username ?: IntegrationCredentials::USERNAME; + $password = $password ?: IntegrationCredentials::PASSWORD; + $apiKey = $apiKey ?: IntegrationCredentials::API_KEY; + $redirectUri = $redirectUri ?: IntegrationCredentials::REDIRECT_URI; } return [ @@ -60,7 +60,7 @@ protected function setUp(): void } /** - * Authenticate with sandbox via the OAuth2 password flow + * Authenticate with integration environment via the OAuth2 password flow */ protected function login(): object { diff --git a/tests/Integration/EncryptedSessionFlowTest.php b/tests/Integration/EncryptedSessionFlowTest.php index a03b3b6..3ac61e7 100644 --- a/tests/Integration/EncryptedSessionFlowTest.php +++ b/tests/Integration/EncryptedSessionFlowTest.php @@ -54,7 +54,6 @@ private function simulateOAuthResponse(FamilySearch $fs, string $token): object // Invoke private oauthResponseHandler $reflection = new ReflectionClass($fs); $method = $reflection->getMethod('oauthResponseHandler'); - $method->setAccessible(true); return $method->invoke($fs, $mockResponse); } diff --git a/tests/Integration/FamilySearchAuthTest.php b/tests/Integration/FamilySearchAuthTest.php index 852ed83..3eb0f10 100644 --- a/tests/Integration/FamilySearchAuthTest.php +++ b/tests/Integration/FamilySearchAuthTest.php @@ -4,10 +4,8 @@ namespace FamilySearch\Tests\Integration; -use VCR\VCR; - /** - * Integration tests for authentication methods + * Integration tests for authentication methods against live integration API */ class FamilySearchAuthTest extends ApiTestCase { @@ -27,23 +25,15 @@ public function testIsAuthenticatedWithoutToken(): void /** * Test isAuthenticated with valid token - * - * @vcr testIsAuthenticated.json */ public function testIsAuthenticatedWithValidToken(): void { - VCR::turnOn(); - VCR::insertCassette('testIsAuthenticated.json'); - $this->assertResponseOK($this->login()); // Now check if authenticated $isAuth = $this->client->isAuthenticated(); $this->assertTrue($isAuth); - - VCR::eject(); - VCR::turnOff(); } /** @@ -58,7 +48,6 @@ public function testIsAuthenticatedWithInvalidToken(): void ]); // With invalid token, API call should fail - // We can't use VCR for this as it would require recording a 401 // Just verify the method exists and returns a boolean $this->assertIsBool($client->isAuthenticated()); } diff --git a/tests/Integration/FamilySearchIntegrationTest.php b/tests/Integration/FamilySearchIntegrationTest.php index 62e65e7..c7464d9 100644 --- a/tests/Integration/FamilySearchIntegrationTest.php +++ b/tests/Integration/FamilySearchIntegrationTest.php @@ -4,197 +4,86 @@ namespace FamilySearch\Tests\Integration; -use VCR\VCR; - /** - * Integration tests using VCR for HTTP recording/replay + * Integration tests against live FamilySearch integration API */ class FamilySearchIntegrationTest extends ApiTestCase { - /** - * @vcr testAuthenticate.json - */ public function testAuthenticate(): void { - VCR::turnOn(); - VCR::insertCassette('testAuthenticate.json'); - $response = $this->login(); $this->assertResponseOK($response); $this->assertResponseData($response); $this->assertArrayHasKey('access_token', $response->data); - - VCR::eject(); - VCR::turnOff(); } - /** - * @vcr testPost.json - */ public function testPost(): void { - VCR::turnOn(); - VCR::insertCassette('testPost.json'); - $this->assertResponseOK($this->login()); $personId = $this->createPerson(); - $this->assertNotNull( - $personId, - 'createPerson() returned null - VCR cassette may not properly replay X-ENTITY-ID header' - ); + $this->assertNotNull($personId); $this->assertNotEmpty($personId); - - VCR::eject(); - VCR::turnOff(); } - /** - * @vcr testGet.json - */ public function testGet(): void { - VCR::turnOn(); - VCR::insertCassette('testGet.json'); - $this->assertResponseOK($this->login()); $personId = $this->createPerson(); - $this->assertNotNull( - $personId, - 'createPerson() returned null - VCR cassette may not properly replay X-ENTITY-ID header' - ); + $this->assertNotNull($personId); $response = $this->client->get('/platform/tree/persons/' . $personId); $this->assertResponseOK($response); $this->assertResponseData($response); - - VCR::eject(); - VCR::turnOff(); } - /** - * @vcr testHead.json - */ public function testHead(): void { - VCR::turnOn(); - VCR::insertCassette('testHead.json'); - $this->assertResponseOK($this->login()); $personId = $this->createPerson(); - $this->assertNotNull( - $personId, - 'createPerson() returned null - VCR cassette may not properly replay X-ENTITY-ID header' - ); + $this->assertNotNull($personId); $response = $this->client->head('/platform/tree/persons/' . $personId); $this->assertResponseOK($response); $this->assertEmpty($response->body); $this->assertEmpty($response->data ?? null); - - VCR::eject(); - VCR::turnOff(); } - /** - * @vcr testDelete.json - */ public function testDelete(): void { - VCR::turnOn(); - VCR::insertCassette('testDelete.json'); - $this->assertResponseOK($this->login()); $personId = $this->createPerson(); - $this->assertNotNull( - $personId, - 'createPerson() returned null - VCR cassette may not properly replay X-ENTITY-ID header' - ); + $this->assertNotNull($personId); $response = $this->client->delete('/platform/tree/persons/' . $personId); $this->assertResponseOK($response); $response = $this->client->get('/platform/tree/persons/' . $personId); $this->assertEquals(410, $response->statusCode); - - VCR::eject(); - VCR::turnOff(); } /** - * Test redirect handling - * - * NOTE: This test is skipped because VCR (HTTP recording library) doesn't - * properly replay redirect responses. The SDK manually follows redirects to - * work around curl_exec() limitations, but VCR intercepts curl at a level - * that breaks this handling. - * - * Redirect behavior IS tested and working: - * - This test passes when run against live FamilySearch API - * - testPendingModification also exercises redirect handling - * - Manual testing confirms redirects work correctly - * - * To test redirects manually with live API, set credentials and run: - * FAMILYSEARCH_USERNAME=xxx FAMILYSEARCH_PASSWORD=xxx FAMILYSEARCH_API_KEY=xxx \ - * vendor/bin/phpunit --filter testRedirect tests/Integration/FamilySearchIntegrationTest.php - * - * @vcr testRedirect.json + * Test redirect handling against live API */ public function testRedirect(): void { - $this->markTestSkipped( - 'VCR does not properly replay redirect responses. ' . - 'Redirect functionality is verified via testPendingModification and manual testing.' - ); - - VCR::turnOn(); - VCR::insertCassette('testRedirect.json'); - $this->assertResponseOK($this->login()); $response = $this->client->get('/platform/tree/current-person'); $this->assertTrue($response->redirected); - $this->assertEquals('https://api-integ.familysearch.org/platform/tree/current-person', $response->originalUrl); - $this->assertEquals('https://api-integ.familysearch.org/platform/tree/persons/KW7G-28J', $response->effectiveUrl); - - VCR::eject(); - VCR::turnOff(); + $this->assertStringContainsString('/platform/tree/current-person', $response->originalUrl); + $this->assertStringContainsString('/platform/tree/persons/', $response->effectiveUrl); } /** - * Test pending modifications header - * - * NOTE: This test is skipped because VCR (HTTP recording library) does not - * reliably replay this workflow. The test creates a person and then queries - * with a pending modification header, which triggers a redirect. The dynamic - * person IDs returned by the live API do not match pre-recorded cassettes, - * causing VCR to make live requests that return 404 errors. - * - * Pending modification functionality IS working: - * - The X-FS-Feature-Tag header is correctly set in requests - * - Manual testing confirms pending modifications work correctly - * - Other tests verify core SDK functionality - * - * To test pending modifications manually with live API, set credentials and run: - * FAMILYSEARCH_USERNAME=xxx FAMILYSEARCH_PASSWORD=xxx FAMILYSEARCH_API_KEY=xxx \ - * vendor/bin/phpunit --filter testPendingModification tests/Integration/FamilySearchIntegrationTest.php - * - * @vcr testPendingModification.json + * Test that pendingModifications config sends X-FS-Feature-Tag header in requests */ public function testPendingModification(): void { - $this->markTestSkipped( - 'VCR does not reliably replay this workflow with dynamic person IDs. ' . - 'Pending modification functionality is verified via manual testing.' - ); - - VCR::turnOn(); - VCR::insertCassette('testPendingModification.json'); - $creds = $this->getCredentials(); $this->client = new \FamilySearch([ 'appKey' => $creds['api_key'], @@ -204,28 +93,20 @@ public function testPendingModification(): void $this->assertResponseOK($this->login()); $personId = $this->createPerson(); - $this->assertNotNull( - $personId, - 'createPerson() returned null - VCR cassette may not properly replay X-ENTITY-ID header' - ); + $this->assertNotNull($personId); + + $response = $this->client->get('/platform/tree/persons/' . $personId); - $response = $this->client->get('/platform/tree/persons-with-relationships?person=' . $personId); $this->assertResponseOK($response); $this->assertResponseData($response); - $this->assertTrue($response->redirected); - VCR::eject(); - VCR::turnOff(); + // Verify the X-FS-Feature-Tag header was sent with the request + $this->assertArrayHasKey('X-FS-Feature-Tag', $response->requestHeaders); + $this->assertStringContainsString('consolidate-redundant-resources', $response->requestHeaders['X-FS-Feature-Tag']); } - /** - * @vcr testUserAgent.json - */ public function testUserAgent(): void { - VCR::turnOn(); - VCR::insertCassette('testUserAgent.json'); - $creds = $this->getCredentials(); $this->client = new \FamilySearch([ 'appKey' => $creds['api_key'], @@ -233,7 +114,7 @@ public function testUserAgent(): void ]); $this->assertResponseOK($this->login()); - $response = $this->client->get('https://httpbin.org/user-agent'); + $response = $this->client->get('/platform/users/current'); $this->assertResponseOK($response); $this->assertResponseData($response); @@ -241,8 +122,5 @@ public function testUserAgent(): void $this->assertStringContainsString('curl', $response->requestHeaders['User-Agent']); $this->assertStringContainsString('PHP', $response->requestHeaders['User-Agent']); $this->assertStringContainsString('myApp/1.2.3', $response->requestHeaders['User-Agent']); - - VCR::eject(); - VCR::turnOff(); } } diff --git a/tests/Integration/SandboxCredentials.example.php b/tests/Integration/IntegrationCredentials.example.php similarity index 77% rename from tests/Integration/SandboxCredentials.example.php rename to tests/Integration/IntegrationCredentials.example.php index 0c9864b..ea26f58 100644 --- a/tests/Integration/SandboxCredentials.example.php +++ b/tests/Integration/IntegrationCredentials.example.php @@ -7,7 +7,7 @@ /** * Example credentials template for integration tests * - * Copy this file to SandboxCredentials.php and fill in your credentials, + * Copy this file to IntegrationCredentials.php and fill in your credentials, * OR set environment variables (recommended): * - FAMILYSEARCH_USERNAME * - FAMILYSEARCH_PASSWORD @@ -15,9 +15,9 @@ * - FAMILYSEARCH_REDIRECT_URI (optional) * * Note: Credentials are NOT stored in this repository. - * Request sandbox access through the FamilySearch developer program. + * Request integration environment access through the FamilySearch developer program. */ -class SandboxCredentials +class IntegrationCredentials { const USERNAME = ''; // Set via FAMILYSEARCH_USERNAME env var const PASSWORD = ''; // Set via FAMILYSEARCH_PASSWORD env var diff --git a/tests/Unit/FamilySearchConfigTest.php b/tests/Unit/FamilySearchConfigTest.php index 094d1e3..ef2d128 100644 --- a/tests/Unit/FamilySearchConfigTest.php +++ b/tests/Unit/FamilySearchConfigTest.php @@ -57,7 +57,7 @@ public function testConstructorWithIntegrationEnvironment(): void $fs = new FamilySearch(['environment' => 'integration']); $redirectUrl = $fs->oauthRedirectURL(); - $this->assertStringContainsString('integration.familysearch.org', $redirectUrl); + $this->assertStringContainsString('identint.familysearch.org', $redirectUrl); } public function testConstructorWithInvalidEnvironmentDefaultsToIntegration(): void @@ -65,7 +65,7 @@ public function testConstructorWithInvalidEnvironmentDefaultsToIntegration(): vo $fs = new FamilySearch(['environment' => 'invalid']); $redirectUrl = $fs->oauthRedirectURL(); - $this->assertStringContainsString('integration.familysearch.org', $redirectUrl); + $this->assertStringContainsString('identint.familysearch.org', $redirectUrl); } public function testConstructorWithRedirectUri(): void @@ -101,4 +101,44 @@ public function testVersionConstant(): void $this->assertIsString(FamilySearch::VERSION); $this->assertMatchesRegularExpression('/^\d+\.\d+\.\d+$/', FamilySearch::VERSION); } + + /** + * Test that platform API URLs are correct for each environment + * We verify this by checking the OAuth redirect URL which uses identHost() + * and by extension checking that the environment configuration is working. + * The platformHost() method follows the same pattern. + */ + public function testPlatformUrlsForProduction(): void + { + $fs = new FamilySearch(['environment' => 'production']); + $redirectUrl = $fs->oauthRedirectURL(); + + // Production identity host should be ident.familysearch.org + $this->assertStringContainsString('ident.familysearch.org', $redirectUrl); + + // Note: platformHost() for production returns https://api.familysearch.org + } + + public function testPlatformUrlsForBeta(): void + { + $fs = new FamilySearch(['environment' => 'beta']); + $redirectUrl = $fs->oauthRedirectURL(); + + // Beta identity host should be identbeta.familysearch.org + $this->assertStringContainsString('identbeta.familysearch.org', $redirectUrl); + + // Note: platformHost() for beta returns https://apibeta.familysearch.org + // This is verified by the corresponding identity URL pattern + } + + public function testPlatformUrlsForIntegration(): void + { + $fs = new FamilySearch(['environment' => 'integration']); + $redirectUrl = $fs->oauthRedirectURL(); + + // Integration identity host should be identint.familysearch.org + $this->assertStringContainsString('identint.familysearch.org', $redirectUrl); + + // Note: platformHost() for integration returns https://api-integ.familysearch.org + } } diff --git a/tests/Unit/FamilySearchEdgeCasesTest.php b/tests/Unit/FamilySearchEdgeCasesTest.php index 5b564a2..d38881d 100644 --- a/tests/Unit/FamilySearchEdgeCasesTest.php +++ b/tests/Unit/FamilySearchEdgeCasesTest.php @@ -39,7 +39,7 @@ public function testConstructorWithInvalidEnvironment(): void // Should default to integration $url = $fs->oauthRedirectURL(); - $this->assertStringContainsString('integration.familysearch.org', $url); + $this->assertStringContainsString('identint.familysearch.org', $url); } public function testConstructorWithEmptyOptions(): void diff --git a/tests/Unit/FamilySearchHttpMethodsTest.php b/tests/Unit/FamilySearchHttpMethodsTest.php index 9b16160..da6dde9 100644 --- a/tests/Unit/FamilySearchHttpMethodsTest.php +++ b/tests/Unit/FamilySearchHttpMethodsTest.php @@ -58,7 +58,7 @@ public function testEnvironmentUrls(): void $environments = [ 'production' => 'ident.familysearch.org', 'beta' => 'identbeta.familysearch.org', - 'integration' => 'integration.familysearch.org' + 'integration' => 'identint.familysearch.org' ]; foreach ($environments as $env => $expectedHost) { diff --git a/tests/Unit/SessionEncryptionTest.php b/tests/Unit/SessionEncryptionTest.php index 9cd34c7..e7b4d4b 100644 --- a/tests/Unit/SessionEncryptionTest.php +++ b/tests/Unit/SessionEncryptionTest.php @@ -47,7 +47,6 @@ private function invokePrivateMethod(FamilySearch $fs, string $methodName, ...$a { $reflection = new ReflectionClass($fs); $method = $reflection->getMethod($methodName); - $method->setAccessible(true); return $method->invoke($fs, ...$args); } @@ -58,7 +57,6 @@ private function getPrivateProperty(FamilySearch $fs, string $propertyName) { $reflection = new ReflectionClass($fs); $property = $reflection->getProperty($propertyName); - $property->setAccessible(true); return $property->getValue($fs); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 031ab07..e9ac93b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -6,18 +6,3 @@ // Load composer dependencies require __DIR__ . '/../vendor/autoload.php'; - -// Configure VCR for integration tests -\VCR\VCR::configure()->addRequestMatcher('custom_headers', function(\VCR\Request $first, \VCR\Request $second){ - $firstHeaders = $first->getHeaders(); - $secondHeaders = $second->getHeaders(); - unset($firstHeaders['User-Agent']); - unset($secondHeaders['User-Agent']); - return count(array_diff_assoc($firstHeaders, $secondHeaders)) === 0; -}); - -\VCR\VCR::configure()->enableRequestMatchers(array('method','url','query_string','body','custom_headers')); -\VCR\VCR::configure()->setMode('once'); -\VCR\VCR::configure()->setCassettePath('tests/fixtures'); -\VCR\VCR::configure()->setBlackList(['vendor']); -\VCR\VCR::configure()->enableLibraryHooks(['curl']); diff --git a/tests/fixtures/person.json b/tests/fixtures/person.json index e9a2b59..d559d94 100644 --- a/tests/fixtures/person.json +++ b/tests/fixtures/person.json @@ -4,54 +4,54 @@ "id" : "34077598-142f-4784-b1e7-329e2b5a0625", "links" : { "description" : { - "href" : "https://sandbox.familysearch.org/platform/sources/descriptions/MMZJ-GMV" + "href" : "https://integration.familysearch.org/platform/sources/descriptions/MMZJ-GMV" }, "source-reference" : { - "href" : "https://sandbox.familysearch.org/platform/tree/persons/L5C2-WYC/source-references/34077598-142f-4784-b1e7-329e2b5a0625" + "href" : "https://integration.familysearch.org/platform/tree/persons/L5C2-WYC/source-references/34077598-142f-4784-b1e7-329e2b5a0625" } }, "attribution" : { "contributor" : { - "resource" : "https://sandbox.familysearch.org/platform/users/agents/MMDZ-8G7", + "resource" : "https://integration.familysearch.org/platform/users/agents/MMDZ-8G7", "resourceId" : "MMDZ-8G7" }, "modified" : 1470770475882 }, - "description" : "https://sandbox.familysearch.org/platform/sources/descriptions/MMZJ-GMV" + "description" : "https://integration.familysearch.org/platform/sources/descriptions/MMZJ-GMV" }, { "id" : "c290dcce-f7eb-447b-8122-c03ae16f9b96", "links" : { "description" : { - "href" : "https://sandbox.familysearch.org/platform/sources/descriptions/MMZJ-GMN" + "href" : "https://integration.familysearch.org/platform/sources/descriptions/MMZJ-GMN" }, "source-reference" : { - "href" : "https://sandbox.familysearch.org/platform/tree/persons/L5C2-WYC/source-references/c290dcce-f7eb-447b-8122-c03ae16f9b96" + "href" : "https://integration.familysearch.org/platform/tree/persons/L5C2-WYC/source-references/c290dcce-f7eb-447b-8122-c03ae16f9b96" } }, "attribution" : { "contributor" : { - "resource" : "https://sandbox.familysearch.org/platform/users/agents/MMDZ-8G7", + "resource" : "https://integration.familysearch.org/platform/users/agents/MMDZ-8G7", "resourceId" : "MMDZ-8G7" }, "modified" : 1470770441164 }, - "description" : "https://sandbox.familysearch.org/platform/sources/descriptions/MMZJ-GMN" + "description" : "https://integration.familysearch.org/platform/sources/descriptions/MMZJ-GMN" } ], "sortKey" : "0000000000", "identifiers" : { - "http://gedcomx.org/Persistent" : [ "https://sandbox.familysearch.org/ark:/61903/4:1:L5C2-WYC" ] + "http://gedcomx.org/Persistent" : [ "https://integration.familysearch.org/ark:/61903/4:1:L5C2-WYC" ] }, "living" : false, "gender" : { "id" : "8b165ed0-5377-4c55-b4c5-1d6407166e3f", "links" : { "conclusion" : { - "href" : "https://sandbox.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/8b165ed0-5377-4c55-b4c5-1d6407166e3f" + "href" : "https://integration.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/8b165ed0-5377-4c55-b4c5-1d6407166e3f" } }, "attribution" : { "contributor" : { - "resource" : "https://sandbox.familysearch.org/platform/users/agents/MMFW-HTT", + "resource" : "https://integration.familysearch.org/platform/users/agents/MMFW-HTT", "resourceId" : "MMFW-HTT" }, "modified" : 1467250315751 @@ -62,12 +62,12 @@ "id" : "54910b54-274d-461c-adda-7190479bc4c7", "links" : { "conclusion" : { - "href" : "https://sandbox.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/54910b54-274d-461c-adda-7190479bc4c7" + "href" : "https://integration.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/54910b54-274d-461c-adda-7190479bc4c7" } }, "attribution" : { "contributor" : { - "resource" : "https://sandbox.familysearch.org/platform/users/agents/MMFW-HTT", + "resource" : "https://integration.familysearch.org/platform/users/agents/MMFW-HTT", "resourceId" : "MMFW-HTT" }, "modified" : 1467250315751 @@ -89,12 +89,12 @@ "id" : "99519cb9-a8bf-4914-9f11-ccf93492b48b", "links" : { "conclusion" : { - "href" : "https://sandbox.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/99519cb9-a8bf-4914-9f11-ccf93492b48b" + "href" : "https://integration.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/99519cb9-a8bf-4914-9f11-ccf93492b48b" } }, "attribution" : { "contributor" : { - "resource" : "https://sandbox.familysearch.org/platform/users/agents/MMFW-HTT", + "resource" : "https://integration.familysearch.org/platform/users/agents/MMFW-HTT", "resourceId" : "MMFW-HTT" }, "modified" : 1467250315751 @@ -126,12 +126,12 @@ "id" : "8e9dd699-4cea-4d30-8ff2-d5d11b171937", "links" : { "conclusion" : { - "href" : "https://sandbox.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/8e9dd699-4cea-4d30-8ff2-d5d11b171937" + "href" : "https://integration.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/8e9dd699-4cea-4d30-8ff2-d5d11b171937" } }, "attribution" : { "contributor" : { - "resource" : "https://sandbox.familysearch.org/platform/users/agents/MMFW-HTT", + "resource" : "https://integration.familysearch.org/platform/users/agents/MMFW-HTT", "resourceId" : "MMFW-HTT" }, "modified" : 1467250315751 @@ -163,12 +163,12 @@ "id" : "80c2512c-d2bc-422c-a0b8-66fb4621c2f7", "links" : { "conclusion" : { - "href" : "https://sandbox.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/80c2512c-d2bc-422c-a0b8-66fb4621c2f7" + "href" : "https://integration.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/80c2512c-d2bc-422c-a0b8-66fb4621c2f7" } }, "attribution" : { "contributor" : { - "resource" : "https://sandbox.familysearch.org/platform/users/agents/MMFW-HTT", + "resource" : "https://integration.familysearch.org/platform/users/agents/MMFW-HTT", "resourceId" : "MMFW-HTT" }, "modified" : 1467250315751 @@ -200,12 +200,12 @@ "id" : "c36105fd-4502-4a35-867c-308623e24299", "links" : { "conclusion" : { - "href" : "https://sandbox.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/c36105fd-4502-4a35-867c-308623e24299" + "href" : "https://integration.familysearch.org/platform/tree/persons/L5C2-WYC/conclusions/c36105fd-4502-4a35-867c-308623e24299" } }, "attribution" : { "contributor" : { - "resource" : "https://sandbox.familysearch.org/platform/users/agents/MMFW-HTT", + "resource" : "https://integration.familysearch.org/platform/users/agents/MMFW-HTT", "resourceId" : "MMFW-HTT" }, "modified" : 1467250315751 @@ -239,23 +239,23 @@ "id" : "SD-L5C2-WYC", "links" : { "description" : { - "href" : "https://sandbox.familysearch.org/platform/sources/descriptions/SD-L5C2-WYC" + "href" : "https://integration.familysearch.org/platform/sources/descriptions/SD-L5C2-WYC" } }, "citations" : [ { "lang" : "en", - "value" : "\"Family Tree,\" database, FamilySearch (http://familysearch.org : modified 09 August 2016, 19:21), entry for Ebenezer Clark(PID https://sandbox.familysearch.org/ark:/61903/4:1:L5C2-WYC); contributed by various users." + "value" : "\"Family Tree,\" database, FamilySearch (http://familysearch.org : modified 09 August 2016, 19:21), entry for Ebenezer Clark(PID https://integration.familysearch.org/ark:/61903/4:1:L5C2-WYC); contributed by various users." } ], "about" : "#L5C2-WYC", "componentOf" : { - "description" : "https://sandbox.familysearch.org/platform/collections/tree" + "description" : "https://integration.familysearch.org/platform/collections/tree" }, "titles" : [ { "value" : "Ebenezer Clark" } ], "resourceType" : "http://gedcomx.org/Person", "identifiers" : { - "http://gedcomx.org/Persistent" : [ "https://sandbox.familysearch.org/ark:/61903/4:1:L5C2-WYC" ] + "http://gedcomx.org/Persistent" : [ "https://integration.familysearch.org/ark:/61903/4:1:L5C2-WYC" ] }, "modified" : 1470770475000, "version" : "136900632758820000" diff --git a/tests/fixtures/testAuthenticate.json b/tests/fixtures/testAuthenticate.json deleted file mode 100644 index b823292..0000000 --- a/tests/fixtures/testAuthenticate.json +++ /dev/null @@ -1,32 +0,0 @@ -[{ - "request": { - "method": "POST", - "url": "https:\/\/integration.familysearch.org\/cis-web\/oauth2\/v3\/token", - "headers": { - "Host": "integration.familysearch.org", - "Content-Type": "application\/x-www-form-urlencoded", - "User-Agent": "FS-PHP-Lite\/1.2.0 curl\/7.35.0 PHP\/5.5.9-1ubuntu4.17" - }, - "body": "grant_type=password&client_id=a02j000000CBv4gAAD&username=sdktester&password=1234sdkpass" - }, - "response": { - "status": { - "http_version": "1.1", - "code": "200", - "message": "OK" - }, - "headers": { - "Server": "Apache-Coyote\/1.1", - "Expires": "Tue, 03 Jul 2001 06:00:00 GMT", - "Last-Modified": "Tue Dec 12 16:05:53 GMT 2017", - "Cache-Control": "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0", - "Pragma": "no-cache", - "Content-Type": "application\/json;charset=ISO-8859-1", - "Content-Language": "en-US", - "Content-Length": "185", - "Date": "Tue, 12 Dec 2017 16:05:53 GMT", - "Connection": "close" - }, - "body": "{\"access_token\":\"USYS6BD43FA8AE0867B68B90C5E59D6A40B0_idses-int02.a.fsglobal.net\",\"token_type\":\"family_search\",\"token\":\"USYS6BD43FA8AE0867B68B90C5E59D6A40B0_idses-int02.a.fsglobal.net\"}" - } -}] \ No newline at end of file diff --git a/tests/fixtures/testDelete.json b/tests/fixtures/testDelete.json deleted file mode 100644 index 99bcb58..0000000 --- a/tests/fixtures/testDelete.json +++ /dev/null @@ -1,131 +0,0 @@ -[{ - "request": { - "method": "POST", - "url": "https:\/\/integration.familysearch.org\/cis-web\/oauth2\/v3\/token", - "headers": { - "Host": "integration.familysearch.org", - "Content-Type": "application\/x-www-form-urlencoded", - "User-Agent": "FS-PHP-Lite\/1.2.0 curl\/7.35.0 PHP\/5.5.9-1ubuntu4.17" - }, - "body": "grant_type=password&client_id=a02j000000CBv4gAAD&username=sdktester&password=1234sdkpass" - }, - "response": { - "status": { - "http_version": "1.1", - "code": "200", - "message": "OK" - }, - "headers": { - "Server": "Apache-Coyote\/1.1", - "Expires": "Tue, 03 Jul 2001 06:00:00 GMT", - "Last-Modified": "Tue Dec 12 16:05:58 GMT 2017", - "Cache-Control": "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0", - "Pragma": "no-cache", - "Content-Type": "application\/json;charset=ISO-8859-1", - "Content-Language": "en-US", - "Content-Length": "185", - "Date": "Tue, 12 Dec 2017 16:05:58 GMT", - "Connection": "close" - }, - "body": "{\"access_token\":\"USYS6E71D2F28053E4712030C7CF208604FD_idses-int02.a.fsglobal.net\",\"token_type\":\"family_search\",\"token\":\"USYS6E71D2F28053E4712030C7CF208604FD_idses-int02.a.fsglobal.net\"}" - } -},{ - "request": { - "method": "POST", - "url": "https:\/\/api-integ.familysearch.org\/platform\/tree\/persons", - "headers": { - "Host": "api-integ.familysearch.org", - "Authorization": "Bearer USYS6E71D2F28053E4712030C7CF208604FD_idses-int02.a.fsglobal.net", - "Accept": "application\/x-fs-v1+json", - "User-Agent": "FS-PHP-Lite\/1.2.0 curl\/7.35.0 PHP\/5.5.9-1ubuntu4.17", - "Content-Type": "application\/x-fs-v1+json" - }, - "body": "{\"persons\":[{\"sources\":[{\"id\":\"34077598-142f-4784-b1e7-329e2b5a0625\",\"links\":{\"description\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/sources\\\/descriptions\\\/MMZJ-GMV\"},\"source-reference\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/tree\\\/persons\\\/L5C2-WYC\\\/source-references\\\/34077598-142f-4784-b1e7-329e2b5a0625\"}},\"attribution\":{\"contributor\":{\"resource\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/users\\\/agents\\\/MMDZ-8G7\",\"resourceId\":\"MMDZ-8G7\"},\"modified\":1470770475882},\"description\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/sources\\\/descriptions\\\/MMZJ-GMV\"},{\"id\":\"c290dcce-f7eb-447b-8122-c03ae16f9b96\",\"links\":{\"description\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/sources\\\/descriptions\\\/MMZJ-GMN\"},\"source-reference\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/tree\\\/persons\\\/L5C2-WYC\\\/source-references\\\/c290dcce-f7eb-447b-8122-c03ae16f9b96\"}},\"attribution\":{\"contributor\":{\"resource\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/users\\\/agents\\\/MMDZ-8G7\",\"resourceId\":\"MMDZ-8G7\"},\"modified\":1470770441164},\"description\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/sources\\\/descriptions\\\/MMZJ-GMN\"}],\"sortKey\":\"0000000000\",\"identifiers\":{\"http:\\\/\\\/gedcomx.org\\\/Persistent\":[\"https:\\\/\\\/sandbox.familysearch.org\\\/ark:\\\/61903\\\/4:1:L5C2-WYC\"]},\"living\":false,\"gender\":{\"id\":\"8b165ed0-5377-4c55-b4c5-1d6407166e3f\",\"links\":{\"conclusion\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/tree\\\/persons\\\/L5C2-WYC\\\/conclusions\\\/8b165ed0-5377-4c55-b4c5-1d6407166e3f\"}},\"attribution\":{\"contributor\":{\"resource\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/users\\\/agents\\\/MMFW-HTT\",\"resourceId\":\"MMFW-HTT\"},\"modified\":1467250315751},\"type\":\"http:\\\/\\\/gedcomx.org\\\/Male\"},\"names\":[{\"id\":\"54910b54-274d-461c-adda-7190479bc4c7\",\"links\":{\"conclusion\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/tree\\\/persons\\\/L5C2-WYC\\\/conclusions\\\/54910b54-274d-461c-adda-7190479bc4c7\"}},\"attribution\":{\"contributor\":{\"resource\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/users\\\/agents\\\/MMFW-HTT\",\"resourceId\":\"MMFW-HTT\"},\"modified\":1467250315751},\"type\":\"http:\\\/\\\/gedcomx.org\\\/BirthName\",\"nameForms\":[{\"fullText\":\"Ebenezer Clark\",\"parts\":[{\"type\":\"http:\\\/\\\/gedcomx.org\\\/Given\",\"value\":\"Ebenezer\"},{\"type\":\"http:\\\/\\\/gedcomx.org\\\/Surname\",\"value\":\"Clark\"}]}],\"preferred\":true}],\"facts\":[{\"id\":\"99519cb9-a8bf-4914-9f11-ccf93492b48b\",\"links\":{\"conclusion\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/tree\\\/persons\\\/L5C2-WYC\\\/conclusions\\\/99519cb9-a8bf-4914-9f11-ccf93492b48b\"}},\"attribution\":{\"contributor\":{\"resource\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/users\\\/agents\\\/MMFW-HTT\",\"resourceId\":\"MMFW-HTT\"},\"modified\":1467250315751},\"type\":\"http:\\\/\\\/gedcomx.org\\\/Birth\",\"date\":{\"original\":\"29 Nov 1651\",\"formal\":\"+1651-11-29\",\"normalized\":[{\"lang\":\"en-US\",\"value\":\"29 November 1651\"},{\"value\":\"29 November 1651\"}]},\"place\":{\"original\":\"NEW HAVEN,NEW HAVEN,CONN\",\"description\":\"#1740247784\",\"normalized\":[{\"lang\":\"en-US\",\"value\":\"New Haven, New Haven, Connecticut, United States\"},{\"value\":\"New Haven, New Haven, Connecticut, United States\"},{\"value\":\"New Haven, New Haven, Connecticut, United States\"}]}},{\"id\":\"8e9dd699-4cea-4d30-8ff2-d5d11b171937\",\"links\":{\"conclusion\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/tree\\\/persons\\\/L5C2-WYC\\\/conclusions\\\/8e9dd699-4cea-4d30-8ff2-d5d11b171937\"}},\"attribution\":{\"contributor\":{\"resource\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/users\\\/agents\\\/MMFW-HTT\",\"resourceId\":\"MMFW-HTT\"},\"modified\":1467250315751},\"type\":\"http:\\\/\\\/gedcomx.org\\\/Death\",\"date\":{\"original\":\"1721\",\"formal\":\"+1721\",\"normalized\":[{\"lang\":\"en-US\",\"value\":\"1721\"},{\"value\":\"1721\"}]},\"place\":{\"original\":\"WALLINGFORD,NEW HAVEN CONN.\",\"description\":\"#1243627151\",\"normalized\":[{\"lang\":\"en-US\",\"value\":\"Wallingford, New Haven, Connecticut, United States\"},{\"value\":\"Wallingford, New Haven, Connecticut, United States\"},{\"value\":\"Wallingford, New Haven, Connecticut, United States\"}]}},{\"id\":\"80c2512c-d2bc-422c-a0b8-66fb4621c2f7\",\"links\":{\"conclusion\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/tree\\\/persons\\\/L5C2-WYC\\\/conclusions\\\/80c2512c-d2bc-422c-a0b8-66fb4621c2f7\"}},\"attribution\":{\"contributor\":{\"resource\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/users\\\/agents\\\/MMFW-HTT\",\"resourceId\":\"MMFW-HTT\"},\"modified\":1467250315751},\"type\":\"http:\\\/\\\/gedcomx.org\\\/Burial\",\"date\":{\"original\":\"1721\",\"formal\":\"+1721\",\"normalized\":[{\"lang\":\"en-US\",\"value\":\"1721\"},{\"value\":\"1721\"}]},\"place\":{\"original\":\"WALLINGFORD,NEW HAVEN CONN.\",\"description\":\"#1243627151\",\"normalized\":[{\"lang\":\"en-US\",\"value\":\"Wallingford, New Haven, Connecticut, United States\"},{\"value\":\"Wallingford, New Haven, Connecticut, United States\"},{\"value\":\"Wallingford, New Haven, Connecticut, United States\"}]}},{\"id\":\"c36105fd-4502-4a35-867c-308623e24299\",\"links\":{\"conclusion\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/tree\\\/persons\\\/L5C2-WYC\\\/conclusions\\\/c36105fd-4502-4a35-867c-308623e24299\"}},\"attribution\":{\"contributor\":{\"resource\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/users\\\/agents\\\/MMFW-HTT\",\"resourceId\":\"MMFW-HTT\"},\"modified\":1467250315751},\"type\":\"http:\\\/\\\/gedcomx.org\\\/Christening\",\"date\":{\"original\":\"29 Nov 1651\",\"formal\":\"+1651-11-29\",\"normalized\":[{\"lang\":\"en-US\",\"value\":\"29 November 1651\"},{\"value\":\"29 November 1651\"}]},\"place\":{\"original\":\"NEW HAVEN,NEW HAVEN,CONN\",\"description\":\"#1740247784\",\"normalized\":[{\"lang\":\"en-US\",\"value\":\"New Haven, New Haven, Connecticut, United States\"},{\"value\":\"New Haven, New Haven, Connecticut, United States\"},{\"value\":\"New Haven, New Haven, Connecticut, United States\"}]}}]}],\"sourceDescriptions\":[{\"id\":\"SD-L5C2-WYC\",\"links\":{\"description\":{\"href\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/sources\\\/descriptions\\\/SD-L5C2-WYC\"}},\"citations\":[{\"lang\":\"en\",\"value\":\"\\\"Family Tree,\\\" database, FamilySearch<\\\/i> (http:\\\/\\\/familysearch.org : modified 09 August 2016, 19:21), entry for Ebenezer Clark(PID https:\\\/\\\/sandbox.familysearch.org\\\/ark:\\\/61903\\\/4:1:L5C2-WYC); contributed by various users.\"}],\"about\":\"#L5C2-WYC\",\"componentOf\":{\"description\":\"https:\\\/\\\/sandbox.familysearch.org\\\/platform\\\/collections\\\/tree\"},\"titles\":[{\"value\":\"Ebenezer Clark\"}],\"resourceType\":\"http:\\\/\\\/gedcomx.org\\\/Person\",\"identifiers\":{\"http:\\\/\\\/gedcomx.org\\\/Persistent\":[\"https:\\\/\\\/sandbox.familysearch.org\\\/ark:\\\/61903\\\/4:1:L5C2-WYC\"]},\"modified\":1470770475000,\"version\":\"136900632758820000\"}]}" - }, - "response": { - "status": { - "http_version": "1.1", - "code": "201", - "message": "Created" - }, - "headers": { - "Cache-Control": "no-cache, no-store, no-transform, must-revalidate, max-age=0", - "Content-Type": "application\/x-fs-v1+json", - "Date": "Tue, 12 Dec 2017 16:05:58 GMT", - "Link": "