blob: fbe0f5f430a0d76cbe692f42c66edc4151a5bb6c [file]
// 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