Skip to content

Commit 08e64aa

Browse files
authored
Merge branch 'main' into vNext
2 parents 35471a5 + 476eb23 commit 08e64aa

File tree

1 file changed

+307
-0
lines changed

1 file changed

+307
-0
lines changed

tests/test_unit_tests.py

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3693,6 +3693,313 @@ def test_builder_sign_dicts_no_auto_add(self):
36933693
# Reset settings
36943694
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":true},"auto_opened_action":{"enabled":true},"auto_created_action":{"enabled":true,"source_type":"http://cv.iptc.org/newscodes/digitalsourcetype/digitalCreation"}}}}')
36953695

3696+
def test_builder_opened_action_one_ingredient_no_auto_add(self):
3697+
"""Test Builder with c2pa.opened action and one ingredient, following Adobe provenance patterns"""
3698+
# Disable auto-added actions
3699+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
3700+
3701+
# Instance IDs for linking ingredients and actions
3702+
# This can be any unique id so the ingredient can be uniquely identified and linked to the action
3703+
parent_ingredient_id = "xmp:iid:a965983b-36fb-445a-aa80-a2d911dcc53c"
3704+
3705+
manifestDefinition = {
3706+
"claim_generator_info": [{
3707+
"name": "Python CAI test",
3708+
"version": "3.14.16"
3709+
}],
3710+
"title": "A title for the provenance test",
3711+
"ingredients": [
3712+
# The parent ingredient will be added through add_ingredient
3713+
# And a properly crafted manifest json so they link
3714+
],
3715+
"assertions": [
3716+
{
3717+
"label": "c2pa.actions.v2",
3718+
"data": {
3719+
"actions": [
3720+
{
3721+
"action": "c2pa.opened",
3722+
"softwareAgent": {
3723+
"name": "Opened asset",
3724+
},
3725+
"parameters": {
3726+
"ingredientIds": [
3727+
parent_ingredient_id
3728+
]
3729+
},
3730+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia"
3731+
}
3732+
]
3733+
}
3734+
}
3735+
]
3736+
}
3737+
3738+
# The ingredient json for the opened action needs to match the instance_id in the manifestDefinition
3739+
# Aka the unique parent_ingredient_id we rely on for linking
3740+
ingredient_json = {
3741+
"relationship": "parentOf",
3742+
"instance_id": parent_ingredient_id
3743+
}
3744+
# An opened ingredient is always a parent, and there can only be exactly one parent ingredient
3745+
3746+
# Read the input file (A.jpg will be signed)
3747+
with open(self.testPath2, "rb") as test_file:
3748+
file_content = test_file.read()
3749+
3750+
builder = Builder.from_json(manifestDefinition)
3751+
3752+
# Add C.jpg as the parent "opened" ingredient
3753+
with open(self.testPath, 'rb') as f:
3754+
builder.add_ingredient(ingredient_json, "image/jpeg", f)
3755+
3756+
output_buffer = io.BytesIO(bytearray())
3757+
builder.sign(
3758+
self.signer,
3759+
"image/jpeg",
3760+
io.BytesIO(file_content),
3761+
output_buffer)
3762+
output_buffer.seek(0)
3763+
3764+
# Read and verify the manifest
3765+
reader = Reader("image/jpeg", output_buffer)
3766+
json_data = reader.json()
3767+
manifest_data = json.loads(json_data)
3768+
3769+
# Verify the ingredient instance ID is present
3770+
self.assertIn(parent_ingredient_id, json_data)
3771+
3772+
# Verify c2pa.opened action is present
3773+
self.assertIn("c2pa.opened", json_data)
3774+
3775+
builder.close()
3776+
3777+
# Make sure settings are put back to the common test defaults
3778+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
3779+
3780+
def test_builder_one_opened_one_placed_action_no_auto_add(self):
3781+
"""Test Builder with c2pa.opened action where asset is its own parent ingredient"""
3782+
# Disable auto-added actions
3783+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
3784+
3785+
# Instance IDs for linking ingredients and actions,
3786+
# need to be unique even if the same binary file is used, so ingredients link properly to actions
3787+
parent_ingredient_id = "xmp:iid:a965983b-36fb-445a-aa80-a2d911dcc53c"
3788+
placed_ingredient_id = "xmp:iid:a965983b-36fb-445a-aa80-f3f800ebe42b"
3789+
3790+
manifestDefinition = {
3791+
"claim_generator_info": [{
3792+
"name": "Python CAI test",
3793+
"version": "0.2.942"
3794+
}],
3795+
"title": "A title for the provenance test",
3796+
"ingredients": [
3797+
# The parent ingredient will be added through add_ingredient
3798+
{
3799+
# Represents the bubbled up AI asset/ingredient
3800+
"format": "jpeg",
3801+
"relationship": "componentOf",
3802+
# Instance ID must be generated to match what is in parameters ingredientIds array
3803+
"instance_id": placed_ingredient_id,
3804+
}
3805+
],
3806+
"assertions": [
3807+
{
3808+
"label": "c2pa.actions.v2",
3809+
"data": {
3810+
"actions": [
3811+
{
3812+
"action": "c2pa.opened",
3813+
"softwareAgent": {
3814+
"name": "Opened asset",
3815+
},
3816+
"parameters": {
3817+
"ingredientIds": [
3818+
parent_ingredient_id
3819+
]
3820+
},
3821+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia"
3822+
},
3823+
{
3824+
"action": "c2pa.placed",
3825+
"softwareAgent": {
3826+
"name": "Placed asset",
3827+
},
3828+
"parameters": {
3829+
"ingredientIds": [
3830+
placed_ingredient_id
3831+
]
3832+
},
3833+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia"
3834+
}
3835+
]
3836+
}
3837+
}
3838+
]
3839+
}
3840+
3841+
# The ingredient json for the opened action needs to match the instance_id in the manifestDefinition for c2pa.opened
3842+
# So that ingredients can link together.
3843+
ingredient_json = {
3844+
"relationship": "parentOf",
3845+
"when": "2025-08-07T18:01:55.934Z",
3846+
"instance_id": parent_ingredient_id
3847+
}
3848+
3849+
# Read the input file (A.jpg will be signed)
3850+
with open(self.testPath2, "rb") as test_file:
3851+
file_content = test_file.read()
3852+
3853+
builder = Builder.from_json(manifestDefinition)
3854+
3855+
# An asset can be its own parent ingredient!
3856+
# We add A.jpg as its own parent ingredient
3857+
with open(self.testPath2, 'rb') as f:
3858+
builder.add_ingredient(ingredient_json, "image/jpeg", f)
3859+
3860+
output_buffer = io.BytesIO(bytearray())
3861+
builder.sign(
3862+
self.signer,
3863+
"image/jpeg",
3864+
io.BytesIO(file_content),
3865+
output_buffer)
3866+
output_buffer.seek(0)
3867+
3868+
# Read and verify the manifest
3869+
reader = Reader("image/jpeg", output_buffer)
3870+
json_data = reader.json()
3871+
manifest_data = json.loads(json_data)
3872+
3873+
# Verify both ingredient instance IDs are present
3874+
self.assertIn(parent_ingredient_id, json_data)
3875+
self.assertIn(placed_ingredient_id, json_data)
3876+
3877+
# Verify both actions are present
3878+
self.assertIn("c2pa.opened", json_data)
3879+
self.assertIn("c2pa.placed", json_data)
3880+
3881+
builder.close()
3882+
3883+
# Make sure settings are put back to the common test defaults
3884+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
3885+
3886+
def test_builder_opened_action_multiple_ingredient_no_auto_add(self):
3887+
"""Test Builder with c2pa.opened and c2pa.placed actions with multiple ingredients"""
3888+
# Disable auto-added actions, as what we are doing here can confuse auto-placements
3889+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
3890+
3891+
# Instance IDs for linking ingredients and actions
3892+
# With multiple ingredients, we need multiple different unique ids so they each link properly
3893+
parent_ingredient_id = "xmp:iid:a965983b-36fb-445a-aa80-a2d911dcc53c"
3894+
placed_ingredient_1_id = "xmp:iid:a965983b-36fb-445a-aa80-f3f800ebe42b"
3895+
placed_ingredient_2_id = "xmp:iid:a965983b-36fb-445a-aa80-f2d712acd14c"
3896+
3897+
manifestDefinition = {
3898+
"claim_generator_info": [{
3899+
"name": "Python CAI test",
3900+
"version": "0.2.942"
3901+
}],
3902+
"title": "A title for the provenance test with multiple ingredients",
3903+
"ingredients": [
3904+
# More ingredients will be added using add_ingredient
3905+
{
3906+
"format": "jpeg",
3907+
"relationship": "componentOf",
3908+
# Instance ID must be generated to match what is in parameters ingredientIds array
3909+
"instance_id": placed_ingredient_1_id,
3910+
}
3911+
],
3912+
"assertions": [
3913+
{
3914+
"label": "c2pa.actions.v2",
3915+
"data": {
3916+
"actions": [
3917+
{
3918+
"action": "c2pa.opened",
3919+
"softwareAgent": {
3920+
"name": "A parent opened asset",
3921+
},
3922+
"parameters": {
3923+
"ingredientIds": [
3924+
parent_ingredient_id
3925+
]
3926+
},
3927+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia"
3928+
},
3929+
{
3930+
"action": "c2pa.placed",
3931+
"softwareAgent": {
3932+
"name": "Component placed assets",
3933+
},
3934+
"parameters": {
3935+
"ingredientIds": [
3936+
placed_ingredient_1_id,
3937+
placed_ingredient_2_id
3938+
]
3939+
},
3940+
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia"
3941+
}
3942+
]
3943+
}
3944+
}
3945+
]
3946+
}
3947+
3948+
# The ingredient json for the opened action needs to match the instance_id in the manifestDefinition,
3949+
# so that ingredients properly link with their action
3950+
ingredient_json_parent = {
3951+
"relationship": "parentOf",
3952+
"instance_id": parent_ingredient_id
3953+
}
3954+
3955+
# The ingredient json for the placed action needs to match the instance_id in the manifestDefinition,
3956+
# so that ingredients properly link with their action
3957+
ingredient_json_placed = {
3958+
"relationship": "componentOf",
3959+
"instance_id": placed_ingredient_2_id
3960+
}
3961+
3962+
# Read the input file (A.jpg will be signed)
3963+
with open(self.testPath2, "rb") as test_file:
3964+
file_content = test_file.read()
3965+
3966+
builder = Builder.from_json(manifestDefinition)
3967+
3968+
# Add C.jpg as the parent ingredient (for c2pa.opened, it's the opened asset)
3969+
with open(self.testPath, 'rb') as f1:
3970+
builder.add_ingredient(ingredient_json_parent, "image/jpeg", f1)
3971+
3972+
# Add cloud.jpg as another placed ingredient (for instance, added on the opened asset)
3973+
with open(self.testPath4, 'rb') as f2:
3974+
builder.add_ingredient(ingredient_json_placed, "image/jpeg", f2)
3975+
3976+
output_buffer = io.BytesIO(bytearray())
3977+
builder.sign(
3978+
self.signer,
3979+
"image/jpeg",
3980+
io.BytesIO(file_content),
3981+
output_buffer)
3982+
output_buffer.seek(0)
3983+
3984+
# Read and verify the manifest
3985+
reader = Reader("image/jpeg", output_buffer)
3986+
json_data = reader.json()
3987+
manifest_data = json.loads(json_data)
3988+
3989+
# Verify all ingredient instance IDs are present
3990+
self.assertIn(parent_ingredient_id, json_data)
3991+
self.assertIn(placed_ingredient_1_id, json_data)
3992+
self.assertIn(placed_ingredient_2_id, json_data)
3993+
3994+
# Verify both actions are present
3995+
self.assertIn("c2pa.opened", json_data)
3996+
self.assertIn("c2pa.placed", json_data)
3997+
3998+
builder.close()
3999+
4000+
# Make sure settings are put back to the common test defaults
4001+
load_settings('{"builder":{"actions":{"auto_placed_action":{"enabled":false},"auto_opened_action":{"enabled":false},"auto_created_action":{"enabled":false}}}}')
4002+
36964003

36974004
class TestStream(unittest.TestCase):
36984005
def setUp(self):

0 commit comments

Comments
 (0)