Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.

Commit d67c5e7

Browse files
authored
chore: tests for simple value classes (#10)
1 parent e5a02e4 commit d67c5e7

14 files changed

+513
-41
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
vendor
22
.php-cs-fixer.cache
33
.phpunit.cache
4+
coverage

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ qa-lowest:
99
vendor/bin/phpstan
1010
vendor/bin/phpunit
1111
git restore composer.lock
12+
13+
coverage:
14+
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage

phpstan.dist.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ parameters:
44
- examples/
55
- src/
66
- tests/
7+
ignoreErrors:
8+
-
9+
message: '#no value type specified in iterable type array#'
10+
path: tests/*
11+

src/Message/Message.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use PhpLlm\LlmChain\Response\ToolCall;
88

9-
final class Message
9+
final readonly class Message implements \JsonSerializable
1010
{
1111
/**
1212
* @param ?ToolCall[] $toolCalls
@@ -46,8 +46,52 @@ public function isSystem(): bool
4646
return Role::System === $this->role;
4747
}
4848

49+
public function isAssistant(): bool
50+
{
51+
return Role::Assistant === $this->role;
52+
}
53+
54+
public function isUser(): bool
55+
{
56+
return Role::User === $this->role;
57+
}
58+
59+
public function isToolCall(): bool
60+
{
61+
return Role::ToolCall === $this->role;
62+
}
63+
4964
public function hasToolCalls(): bool
5065
{
5166
return null !== $this->toolCalls && 0 !== count($this->toolCalls);
5267
}
68+
69+
/**
70+
* @return array{
71+
* role: 'system'|'assistant'|'user'|'tool',
72+
* content: ?string,
73+
* tool_calls?: ToolCall[],
74+
* tool_call_id?: string
75+
* }
76+
*/
77+
public function jsonSerialize(): array
78+
{
79+
$array = [
80+
'role' => $this->role->value,
81+
];
82+
83+
if (null !== $this->content) {
84+
$array['content'] = $this->content;
85+
}
86+
87+
if ($this->hasToolCalls() && $this->isToolCall()) {
88+
$array['tool_call_id'] = $this->toolCalls[0]->id;
89+
}
90+
91+
if ($this->hasToolCalls() && $this->isAssistant()) {
92+
$array['tool_calls'] = $this->toolCalls;
93+
}
94+
95+
return $array;
96+
}
5397
}

src/Message/MessageBag.php

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@
66

77
/**
88
* @template-extends \ArrayObject<int, Message>
9-
*
10-
* @phpstan-type MessageBagData array<int, array{
11-
* role: 'system'|'assistant'|'user'|'function',
12-
* content: ?string,
13-
* function_call?: array{name: string, arguments: string},
14-
* name?: string
15-
* }>
169
*/
1710
final class MessageBag extends \ArrayObject implements \JsonSerializable
1811
{
@@ -59,39 +52,10 @@ public function prepend(Message $message): self
5952
}
6053

6154
/**
62-
* @return MessageBagData
63-
*/
64-
public function toArray(): array
65-
{
66-
return array_map(
67-
function (Message $message) {
68-
$array = [
69-
'role' => $message->role->value,
70-
];
71-
72-
if (null !== $message->content) {
73-
$array['content'] = $message->content;
74-
}
75-
76-
if (null !== $message->hasToolCalls() && Role::ToolCall === $message->role) {
77-
$array['tool_call_id'] = $message->toolCalls[0]->id;
78-
}
79-
80-
if (null !== $message->hasToolCalls() && Role::Assistant === $message->role) {
81-
$array['tool_calls'] = $message->toolCalls;
82-
}
83-
84-
return $array;
85-
},
86-
$this->getArrayCopy(),
87-
);
88-
}
89-
90-
/**
91-
* @return MessageBagData
55+
* @return Message[]
9256
*/
9357
public function jsonSerialize(): array
9458
{
95-
return $this->toArray();
59+
return $this->getArrayCopy();
9660
}
9761
}

src/Response/ToolCall.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
public function __construct(
1313
public string $id,
1414
public string $name,
15-
public array $arguments,
15+
public array $arguments = [],
1616
) {
1717
}
1818

tests/Message/MessageBagTest.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Message;
6+
7+
use PhpLlm\LlmChain\Message\Message;
8+
use PhpLlm\LlmChain\Message\MessageBag;
9+
use PHPUnit\Framework\Attributes\CoversClass;
10+
use PHPUnit\Framework\Attributes\Small;
11+
use PHPUnit\Framework\Attributes\UsesClass;
12+
use PHPUnit\Framework\TestCase;
13+
14+
#[CoversClass(MessageBag::class)]
15+
#[UsesClass(Message::class)]
16+
#[Small]
17+
final class MessageBagTest extends TestCase
18+
{
19+
public function testGetSystemMessage(): void
20+
{
21+
$messageBag = new MessageBag(
22+
Message::forSystem('My amazing system prompt.'),
23+
Message::ofAssistant('It is time to sleep.'),
24+
Message::ofUser('Hello, world!'),
25+
);
26+
27+
$systemMessage = $messageBag->getSystemMessage();
28+
29+
self::assertSame('My amazing system prompt.', $systemMessage->content);
30+
}
31+
32+
public function testGetSystemMessageWithoutSystemMessage(): void
33+
{
34+
$messageBag = new MessageBag(
35+
Message::ofAssistant('It is time to sleep.'),
36+
Message::ofUser('Hello, world!'),
37+
);
38+
39+
self::assertNull($messageBag->getSystemMessage());
40+
}
41+
42+
public function testWith(): void
43+
{
44+
$messageBag = new MessageBag(
45+
Message::forSystem('My amazing system prompt.'),
46+
Message::ofAssistant('It is time to sleep.'),
47+
Message::ofUser('Hello, world!'),
48+
);
49+
50+
$newMessage = Message::ofAssistant('It is time to wake up.');
51+
$newMessageBag = $messageBag->with($newMessage);
52+
53+
self::assertCount(3, $messageBag);
54+
self::assertCount(4, $newMessageBag);
55+
self::assertSame('It is time to wake up.', $newMessageBag[3]->content);
56+
}
57+
58+
public function testWithoutSystemMessage(): void
59+
{
60+
$messageBag = new MessageBag(
61+
Message::forSystem('My amazing system prompt.'),
62+
Message::ofAssistant('It is time to sleep.'),
63+
Message::ofUser('Hello, world!'),
64+
);
65+
66+
$newMessageBag = $messageBag->withoutSystemMessage();
67+
68+
self::assertCount(3, $messageBag);
69+
self::assertCount(2, $newMessageBag);
70+
self::assertSame('It is time to sleep.', $newMessageBag[0]->content);
71+
}
72+
73+
public function testPrepend(): void
74+
{
75+
$messageBag = new MessageBag(
76+
Message::ofAssistant('It is time to sleep.'),
77+
Message::ofUser('Hello, world!'),
78+
);
79+
80+
$newMessage = Message::forSystem('My amazing system prompt.');
81+
$newMessageBag = $messageBag->prepend($newMessage);
82+
83+
self::assertCount(2, $messageBag);
84+
self::assertCount(3, $newMessageBag);
85+
self::assertSame('My amazing system prompt.', $newMessageBag[0]->content);
86+
}
87+
88+
public function testJsonSerialize(): void
89+
{
90+
$messageBag = new MessageBag(
91+
Message::forSystem('My amazing system prompt.'),
92+
Message::ofAssistant('It is time to sleep.'),
93+
Message::ofUser('Hello, world!'),
94+
);
95+
96+
$json = json_encode($messageBag);
97+
98+
self::assertJson($json);
99+
self::assertJsonStringEqualsJsonString(
100+
json_encode([
101+
['role' => 'system', 'content' => 'My amazing system prompt.'],
102+
['role' => 'assistant', 'content' => 'It is time to sleep.'],
103+
['role' => 'user', 'content' => 'Hello, world!'],
104+
]),
105+
$json
106+
);
107+
}
108+
}

tests/Message/MessageTest.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Message;
6+
7+
use PhpLlm\LlmChain\Message\Message;
8+
use PhpLlm\LlmChain\Response\ToolCall;
9+
use PHPUnit\Framework\Attributes\CoversClass;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use PHPUnit\Framework\Attributes\Small;
12+
use PHPUnit\Framework\Attributes\UsesClass;
13+
use PHPUnit\Framework\TestCase;
14+
15+
#[CoversClass(Message::class)]
16+
#[UsesClass(ToolCall::class)]
17+
#[Small]
18+
final class MessageTest extends TestCase
19+
{
20+
public function testCreateSystemMessage(): void
21+
{
22+
$message = Message::forSystem('My amazing system prompt.');
23+
24+
self::assertSame('My amazing system prompt.', $message->content);
25+
self::assertTrue($message->isSystem());
26+
self::assertFalse($message->isAssistant());
27+
self::assertFalse($message->isUser());
28+
self::assertFalse($message->isToolCall());
29+
self::assertFalse($message->hasToolCalls());
30+
}
31+
32+
public function testCreateAssistantMessage(): void
33+
{
34+
$message = Message::ofAssistant('It is time to sleep.');
35+
36+
self::assertSame('It is time to sleep.', $message->content);
37+
self::assertFalse($message->isSystem());
38+
self::assertTrue($message->isAssistant());
39+
self::assertFalse($message->isUser());
40+
self::assertFalse($message->isToolCall());
41+
self::assertFalse($message->hasToolCalls());
42+
}
43+
44+
public function testCreateAssistantMessageWithToolCalls(): void
45+
{
46+
$toolCalls = [
47+
new ToolCall('call_123456', 'my_tool', ['foo' => 'bar']),
48+
new ToolCall('call_456789', 'my_faster_tool'),
49+
];
50+
$message = Message::ofAssistant(toolCalls: $toolCalls);
51+
52+
self::assertCount(2, $message->toolCalls);
53+
self::assertFalse($message->isSystem());
54+
self::assertTrue($message->isAssistant());
55+
self::assertFalse($message->isUser());
56+
self::assertFalse($message->isToolCall());
57+
self::assertTrue($message->hasToolCalls());
58+
}
59+
60+
public function testCreateUserMessage(): void
61+
{
62+
$message = Message::ofUser('Hi, my name is John.');
63+
64+
self::assertSame('Hi, my name is John.', $message->content);
65+
self::assertFalse($message->isSystem());
66+
self::assertFalse($message->isAssistant());
67+
self::assertTrue($message->isUser());
68+
self::assertFalse($message->isToolCall());
69+
self::assertFalse($message->hasToolCalls());
70+
}
71+
72+
public function testCreateToolCallMessage(): void
73+
{
74+
$toolCall = new ToolCall('call_123456', 'my_tool', ['foo' => 'bar']);
75+
$message = Message::ofToolCall($toolCall, 'Foo bar.');
76+
77+
self::assertSame('Foo bar.', $message->content);
78+
self::assertCount(1, $message->toolCalls);
79+
self::assertFalse($message->isSystem());
80+
self::assertFalse($message->isAssistant());
81+
self::assertFalse($message->isUser());
82+
self::assertTrue($message->isToolCall());
83+
self::assertTrue($message->hasToolCalls());
84+
}
85+
86+
#[DataProvider('provideJsonScenarios')]
87+
public function testJsonSerialize(Message $message, array $expected): void
88+
{
89+
self::assertSame($expected, $message->jsonSerialize());
90+
}
91+
92+
public static function provideJsonScenarios(): array
93+
{
94+
$toolCall1 = new ToolCall('call_123456', 'my_tool', ['foo' => 'bar']);
95+
$toolCall2 = new ToolCall('call_456789', 'my_faster_tool');
96+
97+
return [
98+
'system' => [
99+
Message::forSystem('My amazing system prompt.'),
100+
[
101+
'role' => 'system',
102+
'content' => 'My amazing system prompt.',
103+
],
104+
],
105+
'assistant' => [
106+
Message::ofAssistant('It is time to sleep.'),
107+
[
108+
'role' => 'assistant',
109+
'content' => 'It is time to sleep.',
110+
],
111+
],
112+
'assistant_with_tool_calls' => [
113+
Message::ofAssistant(toolCalls: [$toolCall1, $toolCall2]),
114+
[
115+
'role' => 'assistant',
116+
'tool_calls' => [$toolCall1, $toolCall2],
117+
],
118+
],
119+
'user' => [
120+
Message::ofUser('Hi, my name is John.'),
121+
[
122+
'role' => 'user',
123+
'content' => 'Hi, my name is John.',
124+
],
125+
],
126+
'tool_call' => [
127+
Message::ofToolCall($toolCall1, 'Foo bar.'),
128+
[
129+
'role' => 'tool',
130+
'content' => 'Foo bar.',
131+
'tool_call_id' => 'call_123456',
132+
],
133+
],
134+
];
135+
}
136+
}

0 commit comments

Comments
 (0)