<?php

use classes\cors_policy;

app_require('classes/cors_policy.php');

it('normalizes URL-like CORS entries to origins', function (): void {
    expect(cors_policy::normalizeOrigin('https://api-v2.truckwash.io/master/api'))
        ->toBe('https://api-v2.truckwash.io');
    expect(cors_policy::normalizeOrigin('https://API-V2.TRUCKWASH.IO/canary/api/'))
        ->toBe('https://api-v2.truckwash.io');
    expect(cors_policy::normalizeOrigin('https://api.truckwash.io:4433/ping'))
        ->toBe('https://api.truckwash.io:4433');
    expect(cors_policy::normalizeOrigin('capacitor://LOCALHOST/index.html'))
        ->toBe('capacitor://localhost');
    expect(cors_policy::normalizeOrigin('ionic://localhost'))
        ->toBe('');
});

it('merges required release and existing frontend origins into configured CORS', function (): void {
    $origins = cors_policy::allowedOrigins('https://example.test/app,https://api-v2.truckwash.io/master/api');

    expect($origins)->toContain('https://api-v2.truckwash.io');
    expect($origins)->toContain('http://localhost:5173');
    expect($origins)->toContain('capacitor://localhost');
    expect($origins)->toContain('https://truckwash.io');
    expect($origins)->not->toContain('https://api-v2.truckwash.io/master/api');
});

it('requires a syntactically exact request origin', function (): void {
    expect(cors_policy::normalizeRequestOrigin('capacitor://localhost'))
        ->toBe('capacitor://localhost');
    expect(cors_policy::normalizeRequestOrigin('https://truckwash.io'))
        ->toBe('https://truckwash.io');

    foreach ([
        'capacitor://user@localhost',
        'capacitor://localhost/',
        'capacitor://localhost/path',
        'capacitor://localhost?query=1',
        'capacitor://localhost#fragment',
    ] as $invalidOrigin) {
        expect(cors_policy::normalizeRequestOrigin($invalidOrigin))->toBe('');
        expect(cors_policy::preflightResponse($invalidOrigin, 'https://truckwash.io')['status'])->toBe(403);
    }
});

it('builds credential-safe normal CORS response headers for allowed origins', function (): void {
    $headers = cors_policy::responseHeaders('http://localhost:5173', 'https://truckwash.io');

    expect($headers['Access-Control-Allow-Origin'])->toBe('http://localhost:5173');
    expect($headers['Access-Control-Allow-Credentials'])->toBe('true');
    expect($headers['Access-Control-Allow-Methods'])->toContain('PATCH');
    expect($headers['Access-Control-Allow-Headers'])->toContain('X-Release-Trace');
    expect($headers['Access-Control-Allow-Headers'])->toContain('Cache-Control');
    expect($headers['Access-Control-Expose-Headers'])->toContain('Server-Timing');
    expect($headers['Access-Control-Max-Age'])->toBe('86400');
    expect($headers['Timing-Allow-Origin'])->toBe('http://localhost:5173');
    expect($headers['Vary'])->toBe('Origin');
});

it('allows the fallback Vite localhost dev origin used after port 5173 is busy', function (): void {
    $preflight = cors_policy::preflightResponse('http://localhost:5174', 'https://truckwash.io');

    expect($preflight['allowed'])->toBeTrue();
    expect($preflight['status'])->toBe(200);
    expect($preflight['headers']['Access-Control-Allow-Origin'])->toBe('http://localhost:5174');
});

it('allows the exact Capacitor iOS application origin', function (): void {
    $preflight = cors_policy::preflightResponse('capacitor://localhost', 'https://truckwash.io');

    expect($preflight['allowed'])->toBeTrue();
    expect($preflight['status'])->toBe(200);
    expect($preflight['headers']['Access-Control-Allow-Origin'])->toBe('capacitor://localhost');
    expect($preflight['headers']['Access-Control-Allow-Credentials'])->toBe('true');
    expect($preflight['headers']['Access-Control-Allow-Methods'])->toContain('POST');
    expect($preflight['headers']['Access-Control-Allow-Headers'])->toContain('Authorization');
    expect($preflight['headers']['Access-Control-Allow-Headers'])->toContain('Content-Type');
    expect($preflight['headers']['Vary'])->toBe('Origin');
    expect($preflight['body'])->toBe('');
});

it('builds preflight CORS response headers for the api-v2 release origin', function (): void {
    $preflight = cors_policy::preflightResponse(
        'https://api-v2.truckwash.io',
        'https://truckwash.io'
    );

    expect($preflight['allowed'])->toBeTrue();
    expect($preflight['status'])->toBe(200);
    expect($preflight['headers']['Access-Control-Allow-Origin'])->toBe('https://api-v2.truckwash.io');
    expect($preflight['headers']['Content-Type'])->toBe('application/json');
    expect($preflight['headers']['Access-Control-Expose-Headers'])->toContain('Server-Timing');
    expect($preflight['headers']['Timing-Allow-Origin'])->toBe('https://api-v2.truckwash.io');
    expect($preflight['body'])->toBe('');
});

it('rejects unknown CORS origins', function (): void {
    $headers = cors_policy::responseHeaders('https://evil.example.test', 'https://truckwash.io');
    $preflight = cors_policy::preflightResponse('https://evil.example.test', 'https://truckwash.io');

    expect($headers)->toBe([]);
    expect($preflight['allowed'])->toBeFalse();
    expect($preflight['status'])->toBe(403);
    expect($preflight['body'])->toContain('CORS origin not allowed');

    $unknownCapacitorOrigin = cors_policy::preflightResponse(
        'capacitor://other-host',
        'https://truckwash.io'
    );
    expect($unknownCapacitorOrigin['allowed'])->toBeFalse();
    expect($unknownCapacitorOrigin['status'])->toBe(403);
});

it('reflects the request origin for wildcard CORS instead of sending credentialed wildcard headers', function (): void {
    $headers = cors_policy::responseHeaders('https://partner.example.test', '*');

    expect($headers['Access-Control-Allow-Origin'])->toBe('https://partner.example.test');
    expect($headers['Access-Control-Allow-Credentials'])->toBe('true');
    expect($headers['Access-Control-Allow-Origin'])->not->toBe('*');
    expect($headers['Timing-Allow-Origin'])->toBe('https://partner.example.test');
});
