| // 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 "validator/tracker.h" |
| |
| #include "absl/strings/string_view.h" |
| #include "constants/status_codes.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "proto/manifest.pb.h" |
| #include "proto/validation_status.pb.h" |
| #include "testing/proto_test_utils.h" |
| |
| namespace credentio { |
| namespace { |
| |
| using ::credentio_testing::EqualsProto; |
| using ::credentio_testing::ParseTextProtoOrDie; |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| using ::testing::Property; |
| |
| constexpr absl::string_view kManifestLabel = "urn:c2pa:9999"; |
| |
| TEST(ValidationTrackerTest, RecordSuccess) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| validation_tracker.RecordSuccess(SuccessStatusCode::kClaimSignatureValidated, |
| {}); |
| EXPECT_FALSE(validation_tracker.HasFailures()); |
| EXPECT_THAT(validation_tracker.GetSuccesses(), |
| ElementsAre("claimSignature.validated")); |
| EXPECT_THAT(validation_tracker.GetSuccessStatuses(), |
| ElementsAre(Property(&ValidationStatus::code, |
| "claimSignature.validated"))); |
| EXPECT_THAT(validation_tracker.GetFailures(), IsEmpty()); |
| EXPECT_THAT(validation_tracker.GetInformationals(), IsEmpty()); |
| EXPECT_THAT(manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>( |
| R"pb(successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999" |
| })pb"))); |
| } |
| |
| TEST(ValidationTrackerTest, RecordInformational) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| validation_tracker.RecordInformational( |
| InformationalStatusCode::kIngredientUnknownProvenance, {}); |
| EXPECT_FALSE(validation_tracker.HasFailures()); |
| EXPECT_THAT(validation_tracker.GetSuccesses(), IsEmpty()); |
| EXPECT_THAT(validation_tracker.GetSuccessStatuses(), IsEmpty()); |
| EXPECT_THAT(validation_tracker.GetInformationals(), |
| ElementsAre("ingredient.unknownProvenance")); |
| EXPECT_THAT(validation_tracker.GetInformationalStatuses(), |
| ElementsAre(Property(&ValidationStatus::code, |
| "ingredient.unknownProvenance"))); |
| EXPECT_THAT(validation_tracker.GetFailures(), IsEmpty()); |
| EXPECT_THAT(manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>( |
| R"pb(informationals { |
| code: "ingredient.unknownProvenance" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999" |
| })pb"))); |
| } |
| |
| TEST(ValidationTrackerTest, RecordInformationalWithMessage) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| validation_tracker.RecordInformational( |
| InformationalStatusCode::kIngredientUnknownProvenance, |
| {.explanation = "some message"}); |
| EXPECT_FALSE(validation_tracker.HasFailures()); |
| EXPECT_THAT(validation_tracker.GetSuccesses(), IsEmpty()); |
| EXPECT_THAT(validation_tracker.GetSuccessStatuses(), IsEmpty()); |
| EXPECT_THAT(validation_tracker.GetInformationals(), |
| ElementsAre("ingredient.unknownProvenance")); |
| EXPECT_THAT(validation_tracker.GetFailures(), IsEmpty()); |
| EXPECT_THAT(manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>( |
| R"pb(informationals { |
| code: "ingredient.unknownProvenance" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999" |
| explanation: "some message" |
| })pb"))); |
| } |
| |
| TEST(ValidationTrackerTest, RecordFailure) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| validation_tracker.RecordFailure(FailureStatusCode::kClaimSignatureMissing, |
| {}); |
| EXPECT_TRUE(validation_tracker.HasFailures()); |
| EXPECT_THAT(validation_tracker.GetSuccesses(), IsEmpty()); |
| EXPECT_THAT(validation_tracker.GetSuccessStatuses(), IsEmpty()); |
| EXPECT_THAT(validation_tracker.GetFailures(), |
| ElementsAre("claimSignature.missing")); |
| EXPECT_THAT(validation_tracker.GetInformationals(), IsEmpty()); |
| EXPECT_THAT(manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>( |
| R"pb(failures { |
| code: "claimSignature.missing" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999" |
| })pb"))); |
| } |
| |
| TEST(ValidationTrackerTest, RecordFailureWithMessage) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| validation_tracker.RecordFailure(FailureStatusCode::kClaimMissing, {}); |
| validation_tracker.RecordFailure(FailureStatusCode::kClaimSignatureMissing, |
| {.explanation = "some message"}); |
| EXPECT_TRUE(validation_tracker.HasFailures()); |
| EXPECT_THAT( |
| validation_tracker.GetFailureStatuses(), |
| ElementsAre(Property(&ValidationStatus::code, "claim.missing"), |
| Property(&ValidationStatus::code, "claimSignature.missing"))); |
| EXPECT_THAT( |
| manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>(R"pb( |
| failures { code: "claim.missing" url: "self#jumbf=/c2pa/urn:c2pa:9999" } |
| failures { |
| code: "claimSignature.missing" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999" |
| explanation: "some message" |
| } |
| )pb"))); |
| } |
| |
| TEST(ValidationTrackerTest, AssertionTracker) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| ValidationTracker assertion_tracker = |
| validation_tracker.AssertionTracker("testlabel.v2"); |
| assertion_tracker.RecordFailure( |
| FailureStatusCode::kAssertionActionMalformed, |
| {.url = "self#jumbf=/url/override", .explanation = "message1"}); |
| assertion_tracker.RecordFailure(FailureStatusCode::kAssertionActionMalformed, |
| {.explanation = "message2"}); |
| EXPECT_TRUE(assertion_tracker.HasFailures()); |
| EXPECT_TRUE(validation_tracker.HasFailures()); |
| EXPECT_THAT( |
| assertion_tracker.GetFailureStatuses(), |
| ElementsAre( |
| Property(&ValidationStatus::code, "assertion.action.malformed"), |
| Property(&ValidationStatus::code, "assertion.action.malformed"))); |
| EXPECT_THAT( |
| manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>(R"pb( |
| failures { |
| code: "assertion.action.malformed" |
| url: "self#jumbf=/url/override" |
| explanation: "message1" |
| } |
| failures { |
| code: "assertion.action.malformed" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999/c2pa.assertions/testlabel.v2" |
| explanation: "message2" |
| } |
| )pb"))); |
| } |
| |
| TEST(ValidationTrackerTest, HasFailures) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| validation_tracker.RecordFailure(FailureStatusCode::kClaimSignatureMissing, |
| {}); |
| validation_tracker.RecordSuccess(SuccessStatusCode::kClaimSignatureValidated, |
| {}); |
| EXPECT_TRUE(validation_tracker.HasFailures()); |
| } |
| |
| TEST(ValidationTrackerTest, NothingRecorded) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| { |
| ValidationTracker validation_tracker(&manifest); |
| } |
| EXPECT_TRUE(manifest.has_validation()); |
| } |
| |
| TEST(ValidationTrackerTest, RecordSuccessFullUrl) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| validation_tracker.RecordSuccess(SuccessStatusCode::kClaimSignatureValidated, |
| {.url = "self#jumbf=/c2pa/urn:c2pa:1234"}); |
| EXPECT_THAT(manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>( |
| R"pb(successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1234" |
| })pb"))); |
| } |
| |
| TEST(ValidationTrackerTest, RecordSuccessPartialUrl) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| validation_tracker.RecordSuccess(SuccessStatusCode::kClaimSignatureValidated, |
| {.url = "suffix"}); |
| EXPECT_THAT(manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>( |
| R"pb(successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999/suffix" |
| })pb"))); |
| } |
| |
| TEST(ValidationTrackerTest, MergeStatuses) { |
| Manifest manifest; |
| manifest.set_label(kManifestLabel); |
| ValidationTracker validation_tracker(&manifest); |
| |
| ValidationStatusSet statuses; |
| ValidationStatus* failure = statuses.add_failures(); |
| failure->set_code("claim.missing"); |
| failure->set_url("some/url"); |
| failure->set_explanation("some explanation"); |
| |
| ValidationStatus* success = statuses.add_successes(); |
| success->set_code("claimSignature.validated"); |
| success->set_url("self#jumbf=/c2pa/other/url"); |
| |
| ValidationStatus* informational = statuses.add_informationals(); |
| informational->set_code("ingredient.unknownProvenance"); |
| |
| validation_tracker.MergeStatuses(statuses); |
| |
| EXPECT_THAT(manifest.validation(), |
| EqualsProto(ParseTextProtoOrDie<ValidationStatusSet>(R"pb( |
| failures { |
| code: "claim.missing" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999/some/url" |
| explanation: "some explanation" |
| } |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/other/url" |
| } |
| informationals { |
| code: "ingredient.unknownProvenance" |
| url: "self#jumbf=/c2pa/urn:c2pa:9999" |
| } |
| )pb"))); |
| } |
| |
| } // namespace |
| } // namespace credentio |