|
20 | 20 | import warnings |
21 | 21 |
|
22 | 22 | from c2pa import Builder, C2paError as Error, Reader, C2paSigningAlg as SigningAlg, C2paSignerInfo, Signer, sdk_version |
23 | | -from c2pa.c2pa import Stream, read_ingredient_file, read_file, sign_file |
| 23 | +from c2pa.c2pa import Stream, read_ingredient_file, read_file, sign_file, load_settings |
24 | 24 |
|
25 | 25 | # Suppress deprecation warnings |
26 | 26 | warnings.filterwarnings("ignore", category=DeprecationWarning) |
@@ -54,6 +54,17 @@ def test_stream_read_and_parse(self): |
54 | 54 | title = manifest_store["manifests"][manifest_store["active_manifest"]]["title"] |
55 | 55 | self.assertEqual(title, "C.jpg") |
56 | 56 |
|
| 57 | + def test_stream_read_string_stream(self): |
| 58 | + with Reader("image/jpeg", self.testPath) as reader: |
| 59 | + json_data = reader.json() |
| 60 | + self.assertIn("C.jpg", json_data) |
| 61 | + |
| 62 | + def test_stream_read_string_stream_and_parse(self): |
| 63 | + with Reader("image/jpeg", self.testPath) as reader: |
| 64 | + manifest_store = json.loads(reader.json()) |
| 65 | + title = manifest_store["manifests"][manifest_store["active_manifest"]]["title"] |
| 66 | + self.assertEqual(title, "C.jpg") |
| 67 | + |
57 | 68 | def test_reader_bad_format(self): |
58 | 69 | with self.assertRaises(Error.NotSupported): |
59 | 70 | with open(self.testPath, "rb") as file: |
@@ -91,6 +102,13 @@ def test_reader_close_cleanup(self): |
91 | 102 | self.assertIsNone(reader._own_stream) |
92 | 103 | # Verify reader is marked as closed |
93 | 104 | self.assertTrue(reader._closed) |
| 105 | + |
| 106 | + def test_resource_to_stream_on_closed_reader(self): |
| 107 | + """Test that resource_to_stream correctly raises error on closed.""" |
| 108 | + reader = Reader("image/jpeg", self.testPath) |
| 109 | + reader.close() |
| 110 | + with self.assertRaises(Error): |
| 111 | + reader.resource_to_stream("", io.BytesIO(bytearray())) |
94 | 112 |
|
95 | 113 | def test_read_all_files(self): |
96 | 114 | """Test reading C2PA metadata from all files in the fixtures/files-for-reading-tests directory""" |
@@ -198,6 +216,11 @@ def setUp(self): |
198 | 216 | ] |
199 | 217 | } |
200 | 218 |
|
| 219 | + def test_reserve_size_on_closed_signer(self): |
| 220 | + self.signer.close() |
| 221 | + with self.assertRaises(Error): |
| 222 | + self.signer.reserve_size() |
| 223 | + |
201 | 224 | def test_streams_sign(self): |
202 | 225 | with open(self.testPath, "rb") as file: |
203 | 226 | builder = Builder(self.manifestDefinition) |
@@ -313,6 +336,17 @@ def test_builder_double_close(self): |
313 | 336 | # Verify builder is closed |
314 | 337 | with self.assertRaises(Error): |
315 | 338 | builder.set_no_embed() |
| 339 | + |
| 340 | + def test_builder_add_ingredient_on_closed_builder(self): |
| 341 | + """Test that exception is raised when trying to add ingredient after close.""" |
| 342 | + builder = Builder(self.manifestDefinition) |
| 343 | + |
| 344 | + builder.close() |
| 345 | + |
| 346 | + with self.assertRaises(Error): |
| 347 | + ingredient_json = '{"test": "ingredient"}' |
| 348 | + with open(self.testPath, 'rb') as f: |
| 349 | + builder.add_ingredient(ingredient_json, "image/jpeg", f) |
316 | 350 |
|
317 | 351 | def test_builder_add_ingredient(self): |
318 | 352 | """Test Builder class operations with a real file.""" |
@@ -395,6 +429,55 @@ def test_builder_sign_with_ingredient(self): |
395 | 429 |
|
396 | 430 | builder.close() |
397 | 431 |
|
| 432 | + def test_builder_sign_with_duplicate_ingredient(self): |
| 433 | + """Test Builder class operations with a real file.""" |
| 434 | + # Test creating builder from JSON |
| 435 | + |
| 436 | + builder = Builder.from_json(self.manifestDefinition) |
| 437 | + assert builder._builder is not None |
| 438 | + |
| 439 | + # Test adding ingredient |
| 440 | + ingredient_json = '{"title": "Test Ingredient"}' |
| 441 | + with open(self.testPath3, 'rb') as f: |
| 442 | + builder.add_ingredient(ingredient_json, "image/jpeg", f) |
| 443 | + builder.add_ingredient(ingredient_json, "image/jpeg", f) |
| 444 | + builder.add_ingredient(ingredient_json, "image/jpeg", f) |
| 445 | + |
| 446 | + with open(self.testPath2, "rb") as file: |
| 447 | + output = io.BytesIO(bytearray()) |
| 448 | + builder.sign(self.signer, "image/jpeg", file, output) |
| 449 | + output.seek(0) |
| 450 | + reader = Reader("image/jpeg", output) |
| 451 | + json_data = reader.json() |
| 452 | + manifest_data = json.loads(json_data) |
| 453 | + |
| 454 | + # Verify active manifest exists |
| 455 | + self.assertIn("active_manifest", manifest_data) |
| 456 | + active_manifest_id = manifest_data["active_manifest"] |
| 457 | + |
| 458 | + # Verify active manifest object exists |
| 459 | + self.assertIn("manifests", manifest_data) |
| 460 | + self.assertIn(active_manifest_id, manifest_data["manifests"]) |
| 461 | + active_manifest = manifest_data["manifests"][active_manifest_id] |
| 462 | + |
| 463 | + # Verify ingredients array exists in active manifest |
| 464 | + self.assertIn("ingredients", active_manifest) |
| 465 | + self.assertIsInstance(active_manifest["ingredients"], list) |
| 466 | + self.assertTrue(len(active_manifest["ingredients"]) > 0) |
| 467 | + |
| 468 | + # Verify the first ingredient's title matches what we set |
| 469 | + first_ingredient = active_manifest["ingredients"][0] |
| 470 | + self.assertEqual(first_ingredient["title"], "Test Ingredient") |
| 471 | + |
| 472 | + # Verify subsequent labels are unique and have a double underscore with a monotonically inc. index |
| 473 | + second_ingredient = active_manifest["ingredients"][1] |
| 474 | + self.assertTrue(second_ingredient["label"].endswith("__1")) |
| 475 | + |
| 476 | + third_ingredient = active_manifest["ingredients"][2] |
| 477 | + self.assertTrue(third_ingredient["label"].endswith("__2")) |
| 478 | + |
| 479 | + builder.close() |
| 480 | + |
398 | 481 | def test_builder_sign_with_ingredient_from_stream(self): |
399 | 482 | """Test Builder class operations with a real file using stream for ingredient.""" |
400 | 483 | # Test creating builder from JSON |
@@ -533,6 +616,37 @@ def test_builder_sign_with_multiple_ingredients_from_stream(self): |
533 | 616 |
|
534 | 617 | builder.close() |
535 | 618 |
|
| 619 | + def test_builder_set_remote_url(self): |
| 620 | + """Test setting the remote url of a builder.""" |
| 621 | + builder = Builder.from_json(self.manifestDefinition) |
| 622 | + builder.set_remote_url("http://this_does_not_exist/foo.jpg") |
| 623 | + |
| 624 | + with open(self.testPath2, "rb") as file: |
| 625 | + output = io.BytesIO(bytearray()) |
| 626 | + builder.sign(self.signer, "image/jpeg", file, output) |
| 627 | + output.seek(0) |
| 628 | + d = output.read() |
| 629 | + self.assertIn(b'provenance="http://this_does_not_exist/foo.jpg"', d) |
| 630 | + |
| 631 | + def test_builder_set_remote_url_no_embed(self): |
| 632 | + """Test setting the remote url of a builder with no embed flag.""" |
| 633 | + builder = Builder.from_json(self.manifestDefinition) |
| 634 | + load_settings(r'{"verify": { "remote_manifest_fetch": false} }') |
| 635 | + builder.set_no_embed() |
| 636 | + builder.set_remote_url("http://this_does_not_exist/foo.jpg") |
| 637 | + |
| 638 | + with open(self.testPath2, "rb") as file: |
| 639 | + output = io.BytesIO(bytearray()) |
| 640 | + builder.sign(self.signer, "image/jpeg", file, output) |
| 641 | + output.seek(0) |
| 642 | + with self.assertRaises(Error) as e: |
| 643 | + Reader("image/jpeg", output) |
| 644 | + |
| 645 | + self.assertIn("http://this_does_not_exist/foo.jpg", e.exception.message) |
| 646 | + |
| 647 | + # Return back to default settings |
| 648 | + load_settings(r'{"verify": { "remote_manifest_fetch": true} }') |
| 649 | + |
536 | 650 | class TestStream(unittest.TestCase): |
537 | 651 | def setUp(self): |
538 | 652 | # Create a temporary file for testing |
@@ -692,6 +806,11 @@ def tearDown(self): |
692 | 806 | import shutil |
693 | 807 | shutil.rmtree(self.temp_data_dir) |
694 | 808 |
|
| 809 | + def test_invalid_settings_str(self): |
| 810 | + """Test loading a malformed settings string.""" |
| 811 | + with self.assertRaises(Error): |
| 812 | + load_settings(r'{"verify": { "remote_manifest_fetch": false }') |
| 813 | + |
695 | 814 | def test_read_ingredient_file(self): |
696 | 815 | """Test reading a C2PA ingredient from a file.""" |
697 | 816 | # Test reading ingredient from file with data_dir |
|
0 commit comments