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