# Testing

You don't need a special test double from this package. The SDK is built on Laravel's own HTTP
client, filesystem, and events, so you test an app that uses it with the tools you already know:
`Http::fake()`, `Storage::fake()`, and the event helpers. There is no `Parse::fake()` to learn.

The examples below use [Pest](https://pestphp.com), Laravel's default test runner, but the
assertions are plain framework calls and read the same under PHPUnit.

## Faking the submission

`Parse::file()->parse()` makes one HTTP call to submit the document. Fake it with `Http::fake()`
so no real request leaves your test, and back the file read with `Storage::fake()`:

```php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use ParseForArtisans\Facades\Parse;
use ParseForArtisans\Models\ParseRequest;

it('submits a document for parsing', function () {
    Storage::fake();
    Storage::disk()->put('contracts/foo.pdf', '%PDF-1.4 fake');

    Http::fake([
        '*/api/v1/parse' => Http::response(['id' => 'parse-123', 'status' => 'pending'], 202),
    ]);

    $parse = Parse::file('contracts/foo.pdf')->parse();

    expect($parse)->toBeInstanceOf(ParseRequest::class)
        ->and($parse->status())->toBe('pending');

    Http::assertSent(fn ($request) => str_contains($request->url(), '/api/v1/parse'));
});
```

`->parse()` returns the local `ParseRequest` handle immediately; the result arrives later through
an event (see below). Asserting the row exists and the request was sent is usually all you need
for the submit path.

---

## Testing your listeners

The parsed Markdown reaches your app through the `ParseCompleted` event, and `ParseFailed` on
error. The most direct way to test what you do with a result is to build a `ParseRequest`, put
the Markdown where `->markdown()` will read it, and dispatch the event. Your listener then runs
exactly as it does in production:

```php
use Illuminate\Support\Facades\Storage;
use ParseForArtisans\Events\ParseCompleted;
use ParseForArtisans\Models\ParseRequest;

it('stores the parsed markdown when a parse completes', function () {
    Storage::fake();

    $parse = ParseRequest::create([
        'disk' => config('filesystems.default'),   // ->markdown() reads from this disk
        'source_path' => 'contracts/foo.pdf',
        'output_path' => 'parsed/contracts/foo.md',
        'status' => 'completed',
        'page_count' => 3,
    ]);

    Storage::disk($parse->disk)->put($parse->output_path, '# Parsed contract');

    ParseCompleted::dispatch($parse);

    // Assert your own side effects: a saved record, a dispatched job, a notification.
    expect($parse->markdown())->toBe('# Parsed contract');
});
```

> Setting `disk` makes `->markdown()` read from the faked disk, which keeps the test free of
> HTTP. In **managed** mode `disk` is null and `->markdown()` fetches the body over the API, so
> fake the `*/api/v1/parse/*/markdown` endpoint with `Http::fake()` instead.

Test the failure path the same way with `ParseFailed`:

```php
use ParseForArtisans\Events\ParseFailed;

it('reports a failed parse', function () {
    $parse = ParseRequest::create([
        'source_path' => 'contracts/foo.pdf',
        'output_path' => 'parsed/contracts/foo.md',
        'status' => 'failed',
        'error' => 'unsupported_type',
    ]);

    ParseFailed::dispatch($parse);

    // Assert your handling, e.g. the document was flagged or someone was alerted.
    expect($parse->error)->toBe('unsupported_type');
});
```

> Prefer to assert that an event *fired* rather than drive a listener? Call `Event::fake()`
> before your code runs and `Event::assertDispatched(ParseCompleted::class)` after. Faking events
> stops your real listeners from running, so pick one approach per test.

---

## Asserting submission errors

Submission problems, such as a bad key, an unsupported type, or an exceeded quota, are
synchronous: the SaaS rejects them and `->parse()` throws
`ParseForArtisans\Exceptions\ParseException`. Fake an error response and assert it:

```php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use ParseForArtisans\Facades\Parse;
use ParseForArtisans\Exceptions\ParseException;

it('throws on an unsupported file type', function () {
    Storage::fake();
    Storage::disk()->put('notes.rtf', 'plain');

    Http::fake([
        '*/api/v1/parse' => Http::response(['error' => ['type' => 'unsupported_type']], 422),
    ]);

    expect(fn () => Parse::file('notes.rtf')->parse())->toThrow(ParseException::class);
});
```

Parse-time problems are different: they arrive later as a `ParseFailed` event, not as a thrown
exception, so test those with the listener pattern above. See
[Handling Results](/docs/handling-results) for the full split between the two error channels.
