|
7 | 7 | Artifact, |
8 | 8 | DataPart, |
9 | 9 | Part, |
| 10 | + TaskArtifactUpdateEvent, |
10 | 11 | TextPart, |
11 | 12 | ) |
12 | 13 | from a2a.utils.artifact import ( |
| 14 | + ArtifactStreamer, |
13 | 15 | get_artifact_text, |
14 | 16 | new_artifact, |
15 | 17 | new_data_artifact, |
@@ -155,5 +157,105 @@ def test_get_artifact_text_empty_parts(self): |
155 | 157 | assert result == '' |
156 | 158 |
|
157 | 159 |
|
| 160 | +class TestArtifactStreamer(unittest.TestCase): |
| 161 | + def setUp(self): |
| 162 | + self.context_id = 'ctx-123' |
| 163 | + self.task_id = 'task-456' |
| 164 | + |
| 165 | + def test_generates_stable_artifact_id(self): |
| 166 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 167 | + e1 = streamer.append('hello ') |
| 168 | + e2 = streamer.append('world') |
| 169 | + self.assertEqual(e1.artifact.artifact_id, e2.artifact.artifact_id) |
| 170 | + |
| 171 | + def test_uses_explicit_artifact_id(self): |
| 172 | + streamer = ArtifactStreamer( |
| 173 | + self.context_id, self.task_id, artifact_id='my-fixed-id' |
| 174 | + ) |
| 175 | + event = streamer.append('chunk') |
| 176 | + self.assertEqual(event.artifact.artifact_id, 'my-fixed-id') |
| 177 | + |
| 178 | + @patch('a2a.utils.artifact.uuid.uuid4') |
| 179 | + def test_generated_id_comes_from_uuid4(self, mock_uuid4): |
| 180 | + mock_uuid = uuid.UUID('abcdef12-1234-5678-1234-567812345678') |
| 181 | + mock_uuid4.return_value = mock_uuid |
| 182 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 183 | + self.assertEqual(streamer.artifact_id, str(mock_uuid)) |
| 184 | + |
| 185 | + def test_default_name_is_response(self): |
| 186 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 187 | + event = streamer.append('text') |
| 188 | + self.assertEqual(event.artifact.name, 'response') |
| 189 | + |
| 190 | + def test_custom_name(self): |
| 191 | + streamer = ArtifactStreamer( |
| 192 | + self.context_id, self.task_id, name='summary' |
| 193 | + ) |
| 194 | + event = streamer.append('text') |
| 195 | + self.assertEqual(event.artifact.name, 'summary') |
| 196 | + |
| 197 | + def test_append_returns_task_artifact_update_event(self): |
| 198 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 199 | + event = streamer.append('chunk') |
| 200 | + self.assertIsInstance(event, TaskArtifactUpdateEvent) |
| 201 | + |
| 202 | + def test_append_sets_correct_context_and_task(self): |
| 203 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 204 | + event = streamer.append('chunk') |
| 205 | + self.assertEqual(event.context_id, self.context_id) |
| 206 | + self.assertEqual(event.task_id, self.task_id) |
| 207 | + |
| 208 | + def test_append_sets_append_true_last_chunk_false(self): |
| 209 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 210 | + event = streamer.append('chunk') |
| 211 | + self.assertTrue(event.append) |
| 212 | + self.assertFalse(event.last_chunk) |
| 213 | + |
| 214 | + def test_append_creates_single_text_part(self): |
| 215 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 216 | + event = streamer.append('hello') |
| 217 | + self.assertEqual(len(event.artifact.parts), 1) |
| 218 | + self.assertIsInstance(event.artifact.parts[0].root, TextPart) |
| 219 | + self.assertEqual(event.artifact.parts[0].root.text, 'hello') |
| 220 | + |
| 221 | + def test_finalize_returns_task_artifact_update_event(self): |
| 222 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 223 | + event = streamer.finalize() |
| 224 | + self.assertIsInstance(event, TaskArtifactUpdateEvent) |
| 225 | + |
| 226 | + def test_finalize_sets_append_true_last_chunk_true(self): |
| 227 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 228 | + event = streamer.finalize() |
| 229 | + self.assertTrue(event.append) |
| 230 | + self.assertTrue(event.last_chunk) |
| 231 | + |
| 232 | + def test_finalize_has_empty_parts(self): |
| 233 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 234 | + event = streamer.finalize() |
| 235 | + self.assertEqual(event.artifact.parts, []) |
| 236 | + |
| 237 | + def test_finalize_uses_same_artifact_id_as_append(self): |
| 238 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 239 | + append_event = streamer.append('text') |
| 240 | + finalize_event = streamer.finalize() |
| 241 | + self.assertEqual( |
| 242 | + append_event.artifact.artifact_id, |
| 243 | + finalize_event.artifact.artifact_id, |
| 244 | + ) |
| 245 | + |
| 246 | + def test_multiple_appends_all_share_artifact_id(self): |
| 247 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 248 | + events = [streamer.append(f'chunk-{i}') for i in range(5)] |
| 249 | + ids = {e.artifact.artifact_id for e in events} |
| 250 | + self.assertEqual(len(ids), 1) |
| 251 | + |
| 252 | + def test_multiple_appends_carry_distinct_text(self): |
| 253 | + streamer = ArtifactStreamer(self.context_id, self.task_id) |
| 254 | + texts = ['Hello, ', 'world', '!'] |
| 255 | + events = [streamer.append(t) for t in texts] |
| 256 | + result_texts = [e.artifact.parts[0].root.text for e in events] |
| 257 | + self.assertEqual(result_texts, texts) |
| 258 | + |
| 259 | + |
158 | 260 | if __name__ == '__main__': |
159 | 261 | unittest.main() |
0 commit comments