push(Middleware::history($history)); $factory = new HttpFactory(); return [new LayaClient(new Client(['handler' => $stack]), $factory, $factory, 'http://laya.test'), $history]; } function json(array $body, int $status = 200): Response { return new Response($status, ['Content-Type' => 'application/json'], json_encode($body)); } function questions(): QuestionSet { return QuestionSet::make()->add(new YesNoQuestion('angry', 'Upset?')); } it('posts the state and questions to /predict', function () { [$client, $history] = fakeClient([json(['answers' => ['angry' => ['type' => 'noul', 'noul' => 0.8]]])]); $result = $client->decide('hello', questions()); $request = $history[0]['request']; expect($request->getMethod())->toBe('POST') ->and((string) $request->getUri())->toBe('http://laya.test/predict') ->and($request->getHeaderLine('Content-Type'))->toBe('application/json') ->and($request->getHeaderLine('Accept'))->toBe('application/json') ->and(json_decode((string) $request->getBody(), true))->toBe([ 'state' => ['body' => 'hello'], 'questions' => ['angry' => ['type' => 'noul', 'instructions' => 'Upset?']], ]) ->and($result->get('angry')->probability())->toBe(0.8); }); it('sends an array state unchanged and includes the model when given', function () { [$client, $history] = fakeClient([json(['answers' => []])]); $client->decide(['subject' => 's', 'body' => 'b'], questions(), 'my-model'); $sent = json_decode((string) $history[0]['request']->getBody(), true); expect($sent['state'])->toBe(['subject' => 's', 'body' => 'b']) ->and($sent['model'])->toBe('my-model'); }); it('omits model when none is given', function () { [$client, $history] = fakeClient([json(['answers' => []])]); $client->decide('x', questions()); expect(json_decode((string) $history[0]['request']->getBody(), true))->not->toHaveKey('model'); }); it('gets /models', function () { [$client, $history] = fakeClient([json(['models' => ['a', 'b']])]); expect($client->models())->toBe(['models' => ['a', 'b']]) ->and($history[0]['request']->getMethod())->toBe('GET') ->and((string) $history[0]['request']->getUri())->toBe('http://laya.test/models'); }); it('throws UnexpectedResponseException with the server detail on HTTP errors', function () { [$client] = fakeClient([json(['detail' => 'bad model'], 422)]); try { $client->decide('x', questions()); $this->fail('expected exception'); } catch (UnexpectedResponseException $e) { expect($e->statusCode())->toBe(422) ->and($e->getMessage())->toBe('Laya returned HTTP 422: bad model'); } }); it('throws JsonException when the body is not JSON', function () { [$client] = fakeClient([new Response(200, [], 'not json')]); $client->models(); })->throws(JsonException::class); it('builds a client through discovery', function () { expect(Laya::client('http://laya.test'))->toBeInstanceOf(LayaClient::class); });