blob: 2d327adcee44df11e1432e4a3510aeb8068a8410 [file]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "assertion/parse_assertion.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/strings/string_view.h"
#include "gmock/gmock.h"
#include "google/protobuf/struct.pb.h"
#include "gtest/gtest.h"
#include "nlohmann/json.hpp"
#include "proto/actions_assertion.pb.h"
#include "proto/ai_disclosure_assertion.pb.h"
#include "proto/assertion.pb.h"
#include "proto/asset_reference_assertion.pb.h"
#include "proto/asset_types_assertion.pb.h"
#include "proto/bmff_based_hash_assertion.pb.h"
#include "proto/boxes_hash_assertion.pb.h"
#include "proto/collection_data_hash_assertion.pb.h"
#include "proto/data_hash_assertion.pb.h"
#include "proto/environmental_sustainability_assertion.pb.h"
#include "proto/ingredient_assertion.pb.h"
#include "proto/repository_receipt_assertion.pb.h"
#include "testing/cbor_utils.h"
#include "testing/proto_test_utils.h"
namespace credentio {
namespace {
using Json = ::nlohmann::json;
using ::absl_testing::IsOk;
using ::absl_testing::StatusIs;
using ::cbor::FromJson;
using ::credentio::ParseStruct;
using ::credentio_testing::EqualsProto;
using ::credentio_testing::ParseTextProtoOrDie;
using ::testing::HasSubstr;
TEST(ParseAssertionTest, ActionsAssertionOkay) {
auto cbor = FromJson(R"json(
{
"allActionsIncluded": true,
"actions": [
{
"action": "c2pa.created",
"softwareAgent": {
"name": "Joe's Photo Editor",
"version": "2.0",
"operating_system": "Windows 10"
},
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
"parameters": {
"instanceID": "ed610ae51f604002be3dbf0c589a2f1f",
"ingredients" : [
{
"url": "self#jumbf=c2pa/joe-ed:urn:uuid:ABCD/c2pa.assertions/c2pa.ingredient.v3__1",
"alg": "sha256",
"hash" : "b64'aGFzaA=='"
},
{
"url": "self#jumbf=c2pa/joe-ed:urn:uuid:EFGH/c2pa.assertions/c2pa.ingredient.v3__2",
"alg": "sha256",
"hash" : "b64'aGFzaA=='"
}
],
"relatedAssertions": [
{
"url": "self#jumbf=c2pa/joe-ed:urn:uuid:ABCD/c2pa.assertions/c2pa.other",
"alg": "sha256",
"hash" : "b64'aGFzaA=='"
}
]
}
},
{
"action": "c2pa.filtered",
"softwareAgent" : {
"name": "Joe's Photo Editor",
"version": "2.0",
"operating_system": "Windows 10"
}
},
{
"action": "c2pa.cropped"
}
],
"templates": [{
"action": "com.joesphoto.filter",
"description": "Magic Filter",
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic",
"softwareAgent" : {
"name": "Joe's Photo Editor",
"version": "2.0",
"schema.org.SoftwareApplication.operatingSystem": "Windows 10"
},
"icon": {
"url": "http://cdn.examplephotoagency.com/logo.svg",
"hash": "b64'aGFzaA=='"
}
}]
})json");
auto expected = ParseTextProtoOrDie<credentio::ActionsAssertion>(R"pb(
all_actions_included: true
actions {
action: "c2pa.created"
digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
software_agent {
name: "Joe\'s Photo Editor"
version: "2.0"
operating_system: "Windows 10"
}
parameters {
instance_id: "ed610ae51f604002be3dbf0c589a2f1f"
ingredients {
url: "self#jumbf=c2pa/joe-ed:urn:uuid:ABCD/c2pa.assertions/c2pa.ingredient.v3__1"
algorithm: "sha256"
hash: "hash"
}
ingredients {
url: "self#jumbf=c2pa/joe-ed:urn:uuid:EFGH/c2pa.assertions/c2pa.ingredient.v3__2"
algorithm: "sha256"
hash: "hash"
}
related_assertions {
url: "self#jumbf=c2pa/joe-ed:urn:uuid:ABCD/c2pa.assertions/c2pa.other"
algorithm: "sha256"
hash: "hash"
}
}
}
actions {
action: "c2pa.filtered"
software_agent {
name: "Joe\'s Photo Editor"
version: "2.0"
operating_system: "Windows 10"
}
}
actions { action: "c2pa.cropped" }
templates {
action: "com.joesphoto.filter"
description: "Magic Filter"
digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic"
software_agent { name: "Joe\'s Photo Editor" version: "2.0" }
icon { url: "http://cdn.examplephotoagency.com/logo.svg" hash: "hash" }
}
)pb");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, EmptyRelatedAssertionsFails) {
auto cbor = FromJson(R"json(
{
"actions": [
{
"action": "c2pa.created",
"softwareAgent": {
"name": "Joe's Photo Editor",
"version": "2.0",
"operating_system": "Windows 10"
},
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
"parameters": {
"instanceID": "ed610ae51f604002be3dbf0c589a2f1f",
"ingredients" : [
{
"url": "self#jumbf=c2pa/joe-ed:urn:uuid:ABCD/c2pa.assertions/c2pa.ingredient.v3__1",
"alg": "sha256",
"hash" : "b64'aGFzaA=='"
},
{
"url": "self#jumbf=c2pa/joe-ed:urn:uuid:EFGH/c2pa.assertions/c2pa.ingredient.v3__2",
"alg": "sha256",
"hash" : "b64'aGFzaA=='"
}
],
"relatedAssertions": []
}
},
{
"action": "c2pa.filtered",
"softwareAgent" : {
"name": "Joe's Photo Editor",
"version": "2.0",
"operating_system": "Windows 10"
}
},
{
"action": "c2pa.cropped"
}
],
"templates": [{
"action": "com.joesphoto.filter",
"description": "Magic Filter",
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic",
"softwareAgent" : {
"name": "Joe's Photo Editor",
"version": "2.0",
"schema.org.SoftwareApplication.operatingSystem": "Windows 10"
},
"icon": {
"url": "http://cdn.examplephotoagency.com/logo.svg",
"hash": "b64'aGFzaA=='"
}
}]
})json");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Field relatedAssertions cannot be empty")));
}
TEST(ParseAssertionTest, ActionV1IngredientField) {
auto cbor = FromJson(R"json(
{
"actions": [
{
"action": "c2pa.opened",
"parameters": {
"ingredient" : {
"url": "self#jumbf=c2pa/joe-ed:urn:uuid:ABCD/c2pa.assertions/c2pa.ingredient.v3",
"alg": "sha256",
"hash" : "b64'aGFzaA=='"
}
}
}
]
})json");
// The `ingredient` field in v1 `parameters-map` is exposed in `ingredients`.
auto expected = ParseTextProtoOrDie<credentio::ActionsAssertionV1>(R"pb(
actions {
action: "c2pa.opened"
parameters {
ingredients {
url: "self#jumbf=c2pa/joe-ed:urn:uuid:ABCD/c2pa.assertions/c2pa.ingredient.v3"
algorithm: "sha256"
hash: "hash"
}
}
}
)pb");
credentio::ActionsAssertionV1 proto;
EXPECT_THAT(ParseAssertion(cbor, &proto), IsOk());
EXPECT_THAT(proto, EqualsProto(expected));
}
TEST(ParseAssertionTest, ActionsAssertionInvalidNotAMap) {
auto cbor = FromJson(R"json([
{
"action": "c2pa.cropped"
}
])json");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR parsed result is not a map")));
}
TEST(ParseAssertionTest, ActionsAssertionInvalidNoAction) {
auto cbor = FromJson(R"json(
{
"actions": [
{
"softwareAgent" : {
"name": "Joe's Photo Editor",
"version": "2.0",
"operating_system": "Windows 10"
}
}
]
})json");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: action")));
}
TEST(ParseAssertionTest, ActionsAssertionInvalidActionNotAString) {
auto cbor = FromJson(R"json(
{
"actions": [
{
"action": 123
}
]
})json");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: action")));
}
TEST(ParseAssertionTest, ActionsAssertionSkipsInvalidDigitalSourceType) {
auto cbor = FromJson(R"json(
{
"actions": [
{
"action": "c2pa.created",
"digitalSourceType": 123
}
]
})json");
auto expected = ParseTextProtoOrDie<credentio::ActionsAssertion>(R"pb(
actions { action: "c2pa.created" }
)pb");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, ActionsAssertionInvalidActionArrayContainsNonMap) {
auto cbor = FromJson(R"json(
{
"actions": [
{
"action": "c2pa.created",
"digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia"
},
123
]
})json");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR item is not a map")));
}
TEST(ParseAssertionTest, ActionsAssertionInvalidMalformedCbor) {
auto cbor = FromJson(R"json(
{
"actions": [
{
"action": "c2pa.created"
}
]
})json");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor.substr(0, 10), &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR parsing failed")));
}
TEST(ParseAssertionTest, ActionsAssertionSkipsInvalidNotArrayActions) {
// Invalid `actions`, as it should be an array, not a map.
auto cbor = FromJson(R"json(
{
"actions":
{
"action": "c2pa.created"
}
})json");
credentio::ActionsAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: actions")));
}
TEST(ParseAssertionTest, IngredientAssertionOkay) {
auto cbor = FromJson(R"json(
{
"dc:title": "ingredient-title",
"dc:format": "ingredient-format",
"relationship": "ingredient-relationship",
"instanceID": "ingredient-instance-id",
"data": {
"url": "ingredient-data-url",
"alg": "ingredient-data-alg",
"hash": "b64'aGFzaA=='",
"size": 1234567890
},
"activeManifest": {
"url": "ingredient-manifest-url",
"alg": "ingredient-manifest-alg",
"hash": "b64'aGFzaA=='"
},
"thumbnail": {
"url": "ingredient-thumbnail-url",
"alg": "ingredient-thumbnail-alg",
"hash": "b64'aGFzaA=='"
},
"validationResults": {
"activeManifest": {
"success": [
{
"code": "status.1",
"url": "url.1",
"explanation": "explanation.1"
},
{
"code": "status.2",
"url": "url.2",
"explanation": "explanation.2"
}
],
"failure": [],
"informational": []
}
},
"description": "ingredient-description",
"informationalURI": "ingredient-informational-uri"
})json");
auto expected = ParseTextProtoOrDie<credentio::IngredientAssertionV3>(R"pb(
title: "ingredient-title"
media_type: "ingredient-format"
relationship: "ingredient-relationship"
instance_id: "ingredient-instance-id"
data {
url: "ingredient-data-url"
algorithm: "ingredient-data-alg"
hash: "hash"
size: 1234567890
}
active_manifest {
url: "ingredient-manifest-url"
algorithm: "ingredient-manifest-alg"
hash: "hash"
}
thumbnail {
url: "ingredient-thumbnail-url"
algorithm: "ingredient-thumbnail-alg"
hash: "hash"
}
validation_results {
active_manifest {
successes: {
code: "status.1"
url: "url.1"
explanation: "explanation.1"
}
successes: {
code: "status.2"
url: "url.2"
explanation: "explanation.2"
}
}
}
description: "ingredient-description"
informational_uri: "ingredient-informational-uri"
)pb");
credentio::IngredientAssertionV3 result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, AssetReferenceAssertionOkay) {
auto cbor = FromJson(R"json(
{
"references": [
{
"description": "A copy of the asset on the web",
"reference": {
"uri": "https://some.storage.us/foo"
}
},
{
"description": "A copy of the asset on IPFS",
"reference": {
"uri": "ipfs://cid"
}
}
]
})json");
auto expected = ParseTextProtoOrDie<credentio::AssetReferenceAssertion>(R"pb(
references {
description: "A copy of the asset on the web"
reference { uri: "https://some.storage.us/foo" }
}
references {
description: "A copy of the asset on IPFS"
reference { uri: "ipfs://cid" }
}
)pb");
credentio::AssetReferenceAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, AssetReferenceAssertionInvalidMissingRequiredField) {
auto cbor = FromJson(R"json(
{
"references": [
{
"reference": {
}
}
]
})json");
credentio::AssetReferenceAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: uri")));
}
TEST(ParseAssertionTest, AssetTypesAssertionOkay) {
auto cbor = FromJson(R"json(
{
"dc:format": "image/jpeg",
"types":
[
{
"type": "c2pa.types.model.tensorflow",
"version": "2.11.0"
},
{
"type": "c2pa.types.savedmodel",
"version": "2.11.0"
}
]
})json");
auto expected = ParseTextProtoOrDie<credentio::AssetTypesAssertion>(R"pb(
media_type: "image/jpeg"
types { type: "c2pa.types.model.tensorflow" version: "2.11.0" }
types { type: "c2pa.types.savedmodel" version: "2.11.0" }
)pb");
credentio::AssetTypesAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, AssetTypesAssertionInvalidMissingRequiredField) {
auto cbor = FromJson(R"json(
{
"types":
[
{
"version": "2.11.0"
}
]
})json");
credentio::AssetTypesAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: type")));
}
TEST(ParseAssertionTest, DataHashAssertionOkay) {
// The `pad` and `pad2` fields are omitted by the parser.
auto cbor = FromJson(R"json(
{
"alg": "sha256",
"pad": "b64'Zm9v'",
"pad2": "b64'Zm9v'",
"hash": "b64'Zm9v'",
"name": "JUMBF manifest",
"exclusions": [
{
"start": 9960,
"length": 4213
},
{
"start": 14173,
"length": 1000
}
]
})json");
auto expected = ParseTextProtoOrDie<credentio::DataHashAssertion>(R"pb(
alg: "sha256"
hash: "foo"
name: "JUMBF manifest"
exclusions { start: 9960 length: 4213 }
exclusions { start: 14173 length: 1000 }
)pb");
credentio::DataHashAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, DataHashAssertionInvalidMissingRequiredField) {
auto cbor = FromJson(R"json(
{
"alg": "sha256"
})json");
credentio::DataHashAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: hash")));
}
TEST(ParseAssertionTest, BmffBasedHashAssertionOkay) {
auto cbor = FromJson(R"json(
{
"hash": "b64'Zm9v'",
"name": "Example `c2pa.hash.bmff.v2` assertion",
"exclusions": [
{
"data": [{ "value": "b64'Zm9v'", "offset": 8 }],
"xpath": "/uuid"
},
{ "xpath": "/ftyp" },
{ "xpath": "/mfra" },
{ "xpath": "/moov[1]/pssh" },
{
"data": [{ "value": "b64'Zm9v'", "offset": 20 }],
"xpath": "/emsg"
}
]
})json");
auto expected = ParseTextProtoOrDie<credentio::BmffBasedHashAssertion>(R"pb(
hash: "foo"
name: "Example `c2pa.hash.bmff.v2` assertion"
exclusions {
data { value: "foo" offset: 8 }
xpath: "/uuid"
}
exclusions { xpath: "/ftyp" }
exclusions { xpath: "/mfra" }
exclusions { xpath: "/moov[1]/pssh" }
exclusions {
data { value: "foo" offset: 20 }
xpath: "/emsg"
}
)pb");
credentio::BmffBasedHashAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, BmffBasedHashAssertionInvalidMissingRequiredField) {
auto cbor = FromJson(R"json(
{
"name": "Example `c2pa.hash.bmff.v2` assertion",
"exclusions": [
{
"data": [
{
"value": "2P7D1hsOSDySl1goh37EgQ==",
"offset": 8
}
]
}
]
})json");
credentio::BmffBasedHashAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: xpath")));
}
TEST(ParseAssertionTest, BoxesHashAssertionOkay) {
auto cbor = FromJson(R"json({
"alg" : "sha256",
"boxes": [
{
"names" : ["SOI", "APP0", "APP2"],
"hash" : "b64'Zm9v'"
},
{
"names" : ["C2PA"],
"hash" : "b64'YmFy'"
}
]
})json");
auto expected = ParseTextProtoOrDie<credentio::BoxesHashAssertion>(R"pb(
alg: "sha256"
boxes { names: "SOI" names: "APP0" names: "APP2" hash: "foo" }
boxes { names: "C2PA" hash: "bar" }
)pb");
credentio::BoxesHashAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, BoxesHashAssertionInvalidMissingRequiredField) {
auto cbor = FromJson(R"json({
"boxes": [
{
"names" : ["SOI", "APP0", "APP2"]
}
]
})json");
credentio::BoxesHashAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: hash")));
}
TEST(ParseAssertionTest, CollectionDataHashAssertionOkay) {
auto cbor = FromJson(R"json(
{
"alg": "sha256",
"uris": [
{
"uri": "foo1.txt",
"hash": "b64'Zm9v'",
"size": 1234,
"dc:format": "text/plain",
"data_types": [
{
"type": "c2pa.types.model.tensorflow",
"version": "2.11.0"
}
]
},
{
"uri": "foo2.jpg",
"hash": "b64'YmFy'",
"dc:format": "image/jpeg"
}
],
"zip_central_directory_hash": "b64'emlwSGFzaA=='"
})json");
auto expected =
ParseTextProtoOrDie<credentio::CollectionDataHashAssertion>(R"pb(
alg: "sha256"
uris {
uri: "foo1.txt"
hash: "foo"
size: 1234
type: "text/plain"
data_types { type: "c2pa.types.model.tensorflow" version: "2.11.0" }
}
uris { uri: "foo2.jpg" hash: "bar" type: "image/jpeg" }
zip_central_directory_hash: "zipHash"
)pb");
credentio::CollectionDataHashAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest,
CollectionDataHashAssertionInvalidMissingRequiredField) {
auto cbor = FromJson(R"json(
{
"uris": [
{
"uri": "foo1.txt",
"hash": "b64'Zm9v'"
}
]
})json");
credentio::CollectionDataHashAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: alg")));
}
TEST(ParseAssertionTest, SoftBindingAssertionOkay) {
auto cbor = FromJson(R"json(
{
"alg": "soft-algorithm",
"name": "soft-binding-name",
"pad": "b64'Zm9v'",
"pad2": "b64'Zm9v'",
"alg-params": "b64'cGFyYW1z'",
"blocks": [
{
"scope": {
"extent": "b64'ZXh0ZW50'",
"timespan": {
"start": 100,
"end": 200
}
},
"value": "b64'dmFsdWU='"
}
],
"bindingMetadata": {
"description": "metadata-desc",
"contact": "metadata-contact",
"informationalUrl": "metadata-url"
}
})json");
auto expected = ParseTextProtoOrDie<credentio::SoftBindingAssertion>(R"pb(
algorithm: "soft-algorithm"
name: "soft-binding-name"
alg_params: "params"
blocks {
scope {
extent: "extent"
timespan { start: 100 end: 200 }
}
value: "value"
}
binding_metadata {
description: "metadata-desc"
contact: "metadata-contact"
informational_url: "metadata-url"
}
)pb");
credentio::SoftBindingAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, SoftBindingAssertionMissingRequiredField) {
auto cbor = FromJson(R"json(
{
"name": "soft-binding-name"
})json");
credentio::SoftBindingAssertion result;
EXPECT_THAT(ParseAssertion(cbor, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Missing required field: alg")));
}
TEST(ParseAssertionTest, ParseStructOkay) {
auto json = R"json({
"string": "bar",
"int": 123,
"float": 123.456,
"bool": true,
"null": null,
"array": [1, 2, 3],
"object": {
"nested": "string"
}
})json";
auto expected = ParseTextProtoOrDie<google::protobuf::Struct>(R"pb(
fields {
key: "string"
value { string_value: "bar" }
}
fields {
key: "int"
value { number_value: 123 }
}
fields {
key: "float"
value { number_value: 123.456 }
}
fields {
key: "bool"
value { bool_value: true }
}
fields {
key: "null"
value { null_value: NULL_VALUE }
}
fields {
key: "array"
value {
list_value {
values { number_value: 1 }
values { number_value: 2 }
values { number_value: 3 }
}
}
}
fields {
key: "object"
value {
struct_value {
fields {
key: "nested"
value { string_value: "string" }
}
}
}
}
)pb");
google::protobuf::Struct result;
EXPECT_THAT(ParseStruct(json, &result), IsOk());
EXPECT_THAT(result, EqualsProto(expected));
}
TEST(ParseAssertionTest, ParseStructInvalidJson) {
auto json = R"json({
"failed
})json";
google::protobuf::Struct result;
EXPECT_THAT(ParseStruct(json, &result),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Invalid JSON: could not parse")));
}
TEST(ParseAssertionTest, AiDisclosureAssertionOkay) {
auto cbor = FromJson(R"json(
{
"modelType": "c2pa.types.model.onnx",
"modelName": "My Awesome AI Model",
"modelIdentifier": "https://example.com/models/awesome-ai-v1",
"contentProfile": {
"humanOversightLevel": "prompt_guided",
"scientificDomain": ["cs.AI", "cs.CV"]
}
})json");
auto expected = ParseTextProtoOrDie<credentio::AiDisclosureAssertion>(R"pb(
model_type: "c2pa.types.model.onnx"
model_name: "My Awesome AI Model"
model_identifier: "https://example.com/models/awesome-ai-v1"
content_profile {
human_oversight_level: "prompt_guided"
scientific_domain: "cs.AI"
scientific_domain: "cs.CV"
}
)pb");
credentio::AiDisclosureAssertion actual;
ASSERT_THAT(credentio::ParseAssertion(cbor, &actual), IsOk());
EXPECT_THAT(actual, EqualsProto(expected));
}
TEST(ParseAssertionTest, EnvironmentalSustainabilityAssertionOkay) {
auto cbor = FromJson(R"json(
{
"energy_kwh": {
"value": 1.23,
"measurementMethod": "com.example.direct-measurement"
},
"carbon_kgco2e": {
"value": 0.456
},
"water_litres": {
"value": 7.89,
"measurementMethod": "com.example.estimation"
}
})json");
auto expected =
ParseTextProtoOrDie<credentio::EnvironmentalSustainabilityAssertion>(R"pb(
energy_kwh {
value: 1.23
measurement_method: "com.example.direct-measurement"
}
carbon_kgco2e { value: 0.456 }
water_litres {
value: 7.89
measurement_method: "com.example.estimation"
}
)pb");
credentio::EnvironmentalSustainabilityAssertion actual;
ASSERT_THAT(credentio::ParseAssertion(cbor, &actual), IsOk());
EXPECT_THAT(actual, EqualsProto(expected));
}
TEST(ParseAssertionTest, RepositoryReceiptAssertionOkay) {
auto json = R"json(
{
"repository": {
"uri": "https://repository.example.com/manifests/xyz123",
"manifestId": "urn:uuid:12345678-1234-5678-1234-567812345678"
},
"anchor": {
"uri": "https://anchor.example.com/receipts/9876",
"parameters": {
"version": "1.0"
},
"proof": {
"signature": "c2lnbmF0dXJl"
}
}
})json";
auto expected =
ParseTextProtoOrDie<credentio::RepositoryReceiptAssertion>(R"pb(
repository {
uri: "https://repository.example.com/manifests/xyz123"
manifest_id: "urn:uuid:12345678-1234-5678-1234-567812345678"
}
anchor {
uri: "https://anchor.example.com/receipts/9876"
parameters {
fields {
key: "version"
value { string_value: "1.0" }
}
}
proof {
fields {
key: "signature"
value { string_value: "c2lnbmF0dXJl" }
}
}
}
)pb");
credentio::RepositoryReceiptAssertion actual;
ASSERT_THAT(credentio::ParseAssertion(json, &actual), IsOk());
EXPECT_THAT(actual, EqualsProto(expected));
}
} // namespace
} // namespace credentio