| // 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 "jumbf/uri.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/no_destructor.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/status/status.h" |
| #include "absl/status/status_macros.h" |
| #include "absl/status/status_matchers.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "jumbf/box.h" |
| #include "uuid/uuid.h" |
| |
| namespace jumbf { |
| namespace { |
| |
| using ::absl_testing::IsOkAndHolds; |
| using ::absl_testing::StatusIs; |
| using ::testing::Eq; |
| |
| constexpr absl::string_view kUuid1 = "DEADBEEF-F00D-BABE-B4DF-000000000001"; |
| |
| DescriptionBox Desc(std::optional<absl::string_view> label) { |
| return DescriptionBox{ |
| .type_uuid = credentio::Uuid::FromStringOrDie(kUuid1), |
| .label = std::move(label), |
| }; |
| } |
| |
| // Concise representation of a SuperBox tree for testing. |
| struct TestSuperBox { |
| std::optional<absl::string_view> label; |
| std::vector<TestSuperBox> children; |
| }; |
| |
| // Creates a payload based on the provided label, backed by a singleton map. |
| // Not thread-safe. |
| absl::string_view TestPayload(std::optional<absl::string_view> label) { |
| static absl::NoDestructor< |
| absl::flat_hash_map<std::string, std::unique_ptr<std::string>>> |
| payloads; |
| std::string label_str = std::string(label.value_or("")); |
| if (auto it = payloads->find(label_str); it != payloads->end()) { |
| return *it->second; |
| } |
| std::string payload = absl::StrCat(label_str, "-payload"); |
| auto [it, _] = payloads->insert( |
| std::make_pair(label_str, std::make_unique<std::string>(payload))); |
| return *it->second; |
| } |
| |
| SuperBox MakeSuperBox(const TestSuperBox& test_box) { |
| SuperBox super_box = {.description = Desc(test_box.label)}; |
| if (test_box.children.empty()) { |
| super_box.contents.push_back( |
| ContentBox{.payload = CborBox{.payload = TestPayload(test_box.label)}}); |
| } |
| for (const TestSuperBox& child : test_box.children) { |
| super_box.contents.push_back(ContentBox{.payload = MakeSuperBox(child)}); |
| } |
| return super_box; |
| } |
| |
| absl::StatusOr<const SuperBox*> Get(const UriResolver& resolver, |
| absl::string_view uri, |
| absl::string_view current_path) { |
| ABSL_ASSIGN_OR_RETURN(auto absolute_path, |
| resolver.GetAbsolutePathFromUri(uri, current_path)); |
| return resolver.ResolvePath(absolute_path); |
| } |
| |
| // Predicate matcher for a SuperBox* with a single CBOR content box. |
| MATCHER_P(HasCborPayload, value, "") { |
| return arg->contents.size() == 1 && |
| std::holds_alternative<CborBox>(arg->contents[0].payload) && |
| std::get<CborBox>(arg->contents[0].payload).payload == value; |
| } |
| |
| TEST(UriTest, RelativePaths) { |
| SuperBox root_box = MakeSuperBox(TestSuperBox{ |
| .label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = std::nullopt}, |
| TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}, |
| TestSuperBox{ |
| .label = "label5", |
| .children = {TestSuperBox{.label = "label6"}}, |
| }}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(Get(resolver, "self#jumbf=label3", "/label1/label2"), |
| IsOkAndHolds(HasCborPayload("label3-payload"))); |
| EXPECT_THAT(Get(resolver, "self#jumbf=label4", "/label1/label2"), |
| IsOkAndHolds(HasCborPayload("label4-payload"))); |
| EXPECT_THAT(Get(resolver, "self#jumbf=label5/label6", "/label1/label2"), |
| IsOkAndHolds(HasCborPayload("label6-payload"))); |
| |
| EXPECT_THAT(Get(resolver, "self#jumbf=label2", "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(Get(resolver, "self#jumbf=label4/label5", "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(Get(resolver, "self#jumbf=label5/label6/", "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT( |
| Get(resolver, "self#jumbf=label5/label6/label7", "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| } |
| |
| TEST(UriTest, AbsolutePaths) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = std::nullopt}, |
| TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT( |
| Get(resolver, "self#jumbf=/label1/label2/label3", "/label1/label2"), |
| IsOkAndHolds(HasCborPayload("label3-payload"))); |
| EXPECT_THAT(Get(resolver, "self#jumbf=/label1/label2/label4", |
| "/label1/label2/label3"), |
| IsOkAndHolds(HasCborPayload("label4-payload"))); |
| |
| EXPECT_THAT(Get(resolver, "self#jumbf=/", "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(Get(resolver, "self#jumbf=/label3", "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(Get(resolver, "self#jumbf=/label1/label2/label3/label5", |
| "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| } |
| |
| TEST(UriTest, EmptyPathNotFound) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(Get(resolver, "self#jumbf=", "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| } |
| |
| TEST(UriTest, AbsolutePathWrongRootPrefix) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(Get(resolver, "self#jumbf=/somethingelse/label2/label3", |
| "/label1/label2"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| } |
| |
| // No self#jumbf= prefix. |
| TEST(UriTest, NoSelfPrefix) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(Get(resolver, "/label1/label2/label3", "/label1/label2"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(UriTest, DotPathComponentsNotSupported) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(Get(resolver, "self#jumbf=/label1/label2/label3/../label4", |
| "/label1/label2"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| EXPECT_THAT(Get(resolver, "self#jumbf=../label1/label2/label3/label4", |
| "/label1/label2"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| EXPECT_THAT( |
| Get(resolver, "self#jumbf=/label1/label2/./label3", "/label1/label2"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(ResolvePathTest, AbsolutePaths) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = std::nullopt}, |
| TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(resolver.ResolvePath("/label1/label2/label3"), |
| IsOkAndHolds(HasCborPayload("label3-payload"))); |
| EXPECT_THAT(resolver.ResolvePath("/label1/label2/label4"), |
| IsOkAndHolds(HasCborPayload("label4-payload"))); |
| |
| EXPECT_THAT(resolver.ResolvePath("/"), StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(resolver.ResolvePath("/label3"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(resolver.ResolvePath("/label1/label2/label3/label5"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| } |
| |
| TEST(ResolvePathTest, EmptyPathNotFound) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(resolver.ResolvePath(""), StatusIs(absl::StatusCode::kNotFound)); |
| } |
| |
| TEST(ResolvePathTest, AbsolutePathWrongRootPrefix) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(resolver.ResolvePath("/somethingelse/label2/label3"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(resolver.ResolvePath("self#jumbf=/label1/label2/label3"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(resolver.ResolvePath("label1/label2/label3"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| } |
| |
| TEST(ResolvePathTest, DotPathComponentsNotSupported) { |
| SuperBox root_box = MakeSuperBox( |
| TestSuperBox{.label = "label1", |
| .children = {TestSuperBox{ |
| .label = "label2", |
| .children = {TestSuperBox{.label = "label3"}, |
| TestSuperBox{.label = "label4"}}}}}); |
| |
| UriResolver resolver = UriResolver::WithSingleRootChild(&root_box); |
| EXPECT_THAT(resolver.ResolvePath("/label1/label2/label3/../label4"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| EXPECT_THAT(resolver.ResolvePath("../label1/label2/label3/label4"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| EXPECT_THAT(resolver.ResolvePath("/label1/label2/./label3"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(GetAbsolutePath, RelativePaths) { |
| EXPECT_THAT(UriResolver::GetAbsolutePath("label3", "/label1/label2"), |
| Eq("/label1/label2/label3")); |
| EXPECT_THAT(UriResolver::GetAbsolutePath("label3/label4", "/label1/label2"), |
| Eq("/label1/label2/label3/label4")); |
| } |
| |
| TEST(GetAbsolutePath, AbsolutePaths) { |
| EXPECT_THAT(UriResolver::GetAbsolutePath("/label3", "/label1/label2"), |
| Eq("/label3")); |
| EXPECT_THAT(UriResolver::GetAbsolutePath("/label3/label4", "/label1/label2"), |
| Eq("/label3/label4")); |
| } |
| |
| TEST(GetAbsolutePathFromUri, RelativePaths) { |
| EXPECT_THAT(UriResolver::GetAbsolutePathFromUri("self#jumbf=label3", |
| "/label1/label2"), |
| IsOkAndHolds("/label1/label2/label3")); |
| EXPECT_THAT(UriResolver::GetAbsolutePathFromUri("self#jumbf=label3/label4", |
| "/label1/label2"), |
| IsOkAndHolds("/label1/label2/label3/label4")); |
| } |
| |
| TEST(GetAbsolutePathFromUri, AbsolutePaths) { |
| EXPECT_THAT(UriResolver::GetAbsolutePathFromUri("self#jumbf=/label3", |
| "/label1/label2"), |
| IsOkAndHolds("/label3")); |
| EXPECT_THAT(UriResolver::GetAbsolutePathFromUri("self#jumbf=/label3/label4", |
| "/label1/label2"), |
| IsOkAndHolds("/label3/label4")); |
| } |
| |
| TEST(GetAbsolutePathFromUri, WrongPrefix) { |
| EXPECT_THAT(UriResolver::GetAbsolutePathFromUri("/label3", "/label1/label2"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| EXPECT_THAT(UriResolver::GetAbsolutePathFromUri("https://google.com/label3", |
| "/label1/label2"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| } // namespace |
| } // namespace jumbf |