| // 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/box_builder.h" | |
| #include <cassert> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_macros.h" // IWYU pragma: keep | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/strings/cord.h" | |
| #include "absl/strings/string_view.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "jumbf/box.h" | |
| #include "jumbf/constants.h" | |
| #include "jumbf/parse.h" | |
| #include "jumbf/test_utils.h" | |
| #include "uuid/uuid.h" | |
| namespace jumbf { | |
| namespace { | |
| using ::absl_testing::StatusIs; | |
| using ::testing::EndsWith; | |
| using ::testing::Eq; | |
| using ::testing::Optional; | |
| TEST(BoxBuilderTest, ContentBox) { | |
| jumbf::SuperBoxBuilder builder(jumbf::kCborBoxTypeUuid, {}); | |
| ABSL_ASSERT_OK(builder.AddContent( | |
| jumbf::kCborBoxType, jumbf::CordFromConstexpr("this is not CBOR"))); | |
| auto built_box_or = std::move(builder).Finalize(); | |
| ABSL_ASSERT_OK(built_box_or); | |
| auto built_box = std::move(*built_box_or); | |
| absl::Cord serialized = std::move(built_box).AsCord(); | |
| absl::string_view serialized_view = serialized.Flatten(); | |
| auto super_box_or = ConsumeSuperBox(&serialized_view, /*recursion_limit=*/-1); | |
| ABSL_ASSERT_OK(super_box_or); | |
| SuperBox super_box = std::move(*super_box_or); | |
| EXPECT_EQ(super_box.description.type_uuid, kCborBoxTypeUuid); | |
| ASSERT_EQ(super_box.contents.size(), 1); | |
| ASSERT_TRUE(super_box.contents[0].Holds<CborBox>()); | |
| EXPECT_EQ(super_box.contents[0].Get<CborBox>().payload, "this is not CBOR"); | |
| } | |
| TEST(BoxBuilderTest, JsonContentBox) { | |
| jumbf::SuperBoxBuilder builder(jumbf::kJsonBoxTypeUuid, {}); | |
| ABSL_ASSERT_OK(builder.AddContent( | |
| jumbf::kJsonBoxType, jumbf::CordFromConstexpr("{\"key\": \"value\"}"))); | |
| auto built_box_or = std::move(builder).Finalize(); | |
| ABSL_ASSERT_OK(built_box_or); | |
| auto built_box = std::move(*built_box_or); | |
| absl::Cord serialized = std::move(built_box).AsCord(); | |
| absl::string_view serialized_view = serialized.Flatten(); | |
| auto super_box_or = ConsumeSuperBox(&serialized_view, /*recursion_limit=*/-1); | |
| ABSL_ASSERT_OK(super_box_or); | |
| SuperBox super_box = std::move(*super_box_or); | |
| EXPECT_EQ(super_box.description.type_uuid, kJsonBoxTypeUuid); | |
| ASSERT_EQ(super_box.contents.size(), 1); | |
| ASSERT_TRUE(super_box.contents[0].Holds<JsonBox>()); | |
| EXPECT_EQ(super_box.contents[0].Get<JsonBox>().payload, | |
| "{\"key\": \"value\"}"); | |
| } | |
| TEST(BoxBuilderTest, InspectLabelAfterFinalize) { | |
| jumbf::SuperBoxBuilder with_label(jumbf::kCborBoxTypeUuid, | |
| {.label = CordFromConstexpr("boxLabel")}); | |
| ABSL_ASSERT_OK(with_label.AddContent( | |
| jumbf::kCborBoxType, jumbf::CordFromConstexpr("this is not CBOR"))); | |
| auto box_or = std::move(with_label).Finalize(); | |
| ABSL_ASSERT_OK(box_or); | |
| jumbf::BuiltSuperBox box = std::move(*box_or); | |
| EXPECT_THAT(box.description_label(), Optional(Eq("boxLabel"))); | |
| } | |
| TEST(BoxBuilderTest, AccessNonexistentLabelAfterFinalize) { | |
| jumbf::SuperBoxBuilder with_label(jumbf::kCborBoxTypeUuid, {}); | |
| ABSL_ASSERT_OK(with_label.AddContent( | |
| jumbf::kCborBoxType, jumbf::CordFromConstexpr("this is not CBOR"))); | |
| auto box_or = std::move(with_label).Finalize(); | |
| ABSL_ASSERT_OK(box_or); | |
| jumbf::BuiltSuperBox box = std::move(*box_or); | |
| EXPECT_THAT(box.description_label(), Eq(std::nullopt)); | |
| } | |
| TEST(BoxBuilderTest, PaddingAndContentBox) { | |
| jumbf::SuperBoxBuilder builder(jumbf::kCborBoxTypeUuid, {}); | |
| ABSL_ASSERT_OK(builder.AddPadding(20)); | |
| ABSL_ASSERT_OK(builder.AddContent( | |
| jumbf::kCborBoxType, jumbf::CordFromConstexpr("this is not CBOR"))); | |
| auto box_or = std::move(builder).Finalize(); | |
| ABSL_ASSERT_OK(box_or); | |
| jumbf::BuiltSuperBox box = std::move(*box_or); | |
| EXPECT_THAT(box.Peek(), EndsWith(std::string(20, 0))); | |
| absl::Cord serialized = std::move(box).AsCord(); | |
| absl::string_view serialized_view = serialized.Flatten(); | |
| auto super_box_or = ConsumeSuperBox(&serialized_view, /*recursion_limit=*/-1); | |
| ABSL_ASSERT_OK(super_box_or); | |
| SuperBox super_box = std::move(*super_box_or); | |
| EXPECT_EQ(super_box.description.type_uuid, kCborBoxTypeUuid); | |
| ASSERT_EQ(super_box.contents.size(), 1); | |
| ASSERT_TRUE(super_box.contents[0].Holds<CborBox>()); | |
| EXPECT_EQ(super_box.contents[0].Get<CborBox>().payload, "this is not CBOR"); | |
| } | |
| TEST(BoxBuilderTest, MultipleContentBox) { | |
| jumbf::SuperBoxBuilder builder(jumbf::kCborBoxTypeUuid, {}); | |
| ABSL_ASSERT_OK(builder.AddContent( | |
| jumbf::kCborBoxType, jumbf::CordFromConstexpr("this is not CBOR"))); | |
| ABSL_ASSERT_OK(builder.AddContent(kBinaryDataBoxType, | |
| jumbf::CordFromConstexpr("I am data!"))); | |
| auto built_box_or = std::move(builder).Finalize(); | |
| ABSL_ASSERT_OK(built_box_or); | |
| auto built_box = std::move(*built_box_or); | |
| absl::Cord serialized = std::move(built_box).AsCord(); | |
| absl::string_view serialized_view = serialized.Flatten(); | |
| auto super_box_or = ConsumeSuperBox(&serialized_view, /*recursion_limit=*/-1); | |
| ABSL_ASSERT_OK(super_box_or); | |
| SuperBox super_box = std::move(*super_box_or); | |
| EXPECT_EQ(super_box.description.type_uuid, kCborBoxTypeUuid); | |
| ASSERT_EQ(super_box.contents.size(), 2); | |
| ASSERT_TRUE(super_box.contents[0].Holds<BinaryDataBox>()); | |
| EXPECT_EQ(super_box.contents[0].Get<BinaryDataBox>().payload, "I am data!"); | |
| ASSERT_TRUE(super_box.contents[1].Holds<CborBox>()); | |
| EXPECT_EQ(super_box.contents[1].Get<CborBox>().payload, "this is not CBOR"); | |
| } | |
| TEST(BoxBuilderTest, NestedSuperBox) { | |
| credentio::Uuid uuid1(0xfeedbabedefec8ed, 0xdeadbeeff00dbabe); | |
| credentio::Uuid uuid2 = jumbf::kCborBoxTypeUuid; | |
| credentio::Uuid uuid3(0xdeadbeeff00dbabe, 0xfeedbabedefec8ed); | |
| jumbf::SuperBoxBuilder first_inner_builder(uuid1, {}); | |
| ABSL_ASSERT_OK(first_inner_builder.AddContent( | |
| jumbf::kCborBoxType, jumbf::CordFromConstexpr("I go in third"))); | |
| auto first_inner_box_or = std::move(first_inner_builder).Finalize(); | |
| ABSL_ASSERT_OK(first_inner_box_or); | |
| jumbf::BuiltSuperBox first_inner_box = std::move(*first_inner_box_or); | |
| jumbf::SuperBoxBuilder second_inner_builder(uuid2, {}); | |
| ABSL_ASSERT_OK(second_inner_builder.AddContent( | |
| jumbf::kCborBoxType, jumbf::CordFromConstexpr("I go in second"))); | |
| auto second_inner_box_or = std::move(second_inner_builder).Finalize(); | |
| ABSL_ASSERT_OK(second_inner_box_or); | |
| BuiltSuperBox second_inner_box = std::move(*second_inner_box_or); | |
| jumbf::SuperBoxBuilder third_inner_builder(uuid3, {}); | |
| ABSL_ASSERT_OK(third_inner_builder.AddContent( | |
| kBinaryDataBoxType, CordFromConstexpr("I go in first"))); | |
| auto third_inner_box_or = std::move(third_inner_builder).Finalize(); | |
| ABSL_ASSERT_OK(third_inner_box_or); | |
| BuiltSuperBox third_inner_box = std::move(*third_inner_box_or); | |
| jumbf::SuperBoxBuilder outer( | |
| credentio::Uuid(0xdefec8edb44df00d, 0xfeedbabef00dbabe), {}); | |
| ABSL_ASSERT_OK(outer.AddChild(std::move(third_inner_box))); | |
| ABSL_ASSERT_OK(outer.AddChild(std::move(second_inner_box))); | |
| ABSL_ASSERT_OK(outer.AddChild(std::move(first_inner_box))); | |
| auto built_box_or = std::move(outer).Finalize(); | |
| ABSL_ASSERT_OK(built_box_or); | |
| auto built_box = std::move(*built_box_or); | |
| absl::Cord serialized = std::move(built_box).AsCord(); | |
| absl::string_view serialized_view = serialized.Flatten(); | |
| auto super_box_or = ConsumeSuperBox(&serialized_view, /*recursion_limit=*/-1); | |
| ABSL_ASSERT_OK(super_box_or); | |
| SuperBox super_box = std::move(*super_box_or); | |
| EXPECT_EQ(super_box.description.type_uuid, | |
| credentio::Uuid(0xdefec8edb44df00d, 0xfeedbabef00dbabe)); | |
| ASSERT_EQ(super_box.contents.size(), 3); | |
| // Box 0: third_inner_box | |
| ASSERT_TRUE(super_box.contents[0].Holds<SuperBox>()); | |
| const auto& nested0 = super_box.contents[0].Get<SuperBox>(); | |
| EXPECT_EQ(nested0.description.type_uuid, uuid1); | |
| ASSERT_EQ(nested0.contents.size(), 1); | |
| ASSERT_TRUE(nested0.contents[0].Holds<CborBox>()); | |
| EXPECT_EQ(nested0.contents[0].Get<CborBox>().payload, "I go in third"); | |
| // Box 1: second_inner_box | |
| ASSERT_TRUE(super_box.contents[1].Holds<SuperBox>()); | |
| const auto& nested1 = super_box.contents[1].Get<SuperBox>(); | |
| EXPECT_EQ(nested1.description.type_uuid, uuid2); | |
| ASSERT_EQ(nested1.contents.size(), 1); | |
| ASSERT_TRUE(nested1.contents[0].Holds<CborBox>()); | |
| EXPECT_EQ(nested1.contents[0].Get<CborBox>().payload, "I go in second"); | |
| // Box 2: first_inner_box | |
| ASSERT_TRUE(super_box.contents[2].Holds<SuperBox>()); | |
| const auto& nested2 = super_box.contents[2].Get<SuperBox>(); | |
| EXPECT_EQ(nested2.description.type_uuid, uuid3); | |
| ASSERT_EQ(nested2.contents.size(), 1); | |
| ASSERT_TRUE(nested2.contents[0].Holds<BinaryDataBox>()); | |
| EXPECT_EQ(nested2.contents[0].Get<BinaryDataBox>().payload, "I go in first"); | |
| } | |
| TEST(BoxBuilderTest, EmptyBoxFails) { | |
| jumbf::SuperBoxBuilder builder(jumbf::kCborBoxTypeUuid, {}); | |
| EXPECT_THAT(std::move(builder).Finalize(), | |
| StatusIs(absl::StatusCode::kFailedPrecondition, | |
| "builder in invalid state for operation: got: EMPTY, " | |
| "want: CONTAINS_CONTENT")); | |
| } | |
| TEST(BoxBuilderTest, MultiplePaddingFails) { | |
| jumbf::SuperBoxBuilder builder(jumbf::kCborBoxTypeUuid, {}); | |
| ABSL_ASSERT_OK(builder.AddPadding(20)); | |
| EXPECT_THAT(builder.AddPadding(22), | |
| StatusIs(absl::StatusCode::kFailedPrecondition, | |
| "builder in invalid state for operation: got: " | |
| "CONTAINS_PADDING, want: EMPTY")); | |
| } | |
| TEST(BoxBuilderTest, PaddingAfterContentFails) { | |
| jumbf::SuperBoxBuilder builder(jumbf::kCborBoxTypeUuid, {}); | |
| ABSL_ASSERT_OK(builder.AddContent( | |
| kBinaryDataBoxType, CordFromConstexpr("bbbbbbbbbbbbbbbbbbbbbb"))); | |
| EXPECT_THAT(builder.AddPadding(22), | |
| StatusIs(absl::StatusCode::kFailedPrecondition, | |
| "builder in invalid state for operation: got: " | |
| "CONTAINS_CONTENT, want: EMPTY")); | |
| } | |
| } // namespace | |
| } // namespace jumbf |