| // 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 |