blob: 36c3b470d1fb0749b32283bd823a57837de78094 [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 "formats/id3/writer.h"
#include <memory>
#include <string>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "absl/log/absl_check.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "async/async_context.h"
#include "formats/byte_range.h"
#include "formats/id3/extractor.h"
#include "generator/generator.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "proto/generator.pb.h"
#include "proto/manifest.pb.h"
#include "proto/validation_status.pb.h"
#include "riegeli/bytes/string_reader.h"
#include "riegeli/bytes/string_writer.h"
#include "testing/nonprod_generator.h"
#include "testing/nonprod_validator.h"
#include "validator/asset_validator.h"
namespace credentio {
namespace {
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::testing::AllOf;
using ::testing::Field;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Optional;
constexpr char kMp3FileWithNoId3Tag[] = {'\xFF', '\xF3', '\x40', '\xC0',
0x01, 0x02, 0x03, 0x04};
class Id3WriterTest : public ::testing::Test {
protected:
void SetUp() override {
auto generator = CreateNonProdManifestStoreGenerator({});
ABSL_CHECK_OK(generator);
generator_ = std::move(*generator);
writer_ = std::make_unique<Id3Writer>(generator_.get());
auto validator = CreateNonProdValidator({});
ABSL_CHECK_OK(validator);
validator_ = std::move(*validator);
}
std::unique_ptr<ManifestStoreGenerator> generator_;
std::unique_ptr<Id3Writer> writer_;
std::unique_ptr<AssetValidator> validator_;
};
TEST_F(Id3WriterTest, Write) {
std::string mp3_file_contents =
std::string(kMp3FileWithNoId3Tag, sizeof(kMp3FileWithNoId3Tag));
riegeli::StringReader source(mp3_file_contents);
ABSL_ASSERT_OK(source.status());
std::string result;
riegeli::StringWriter destination(&result);
ABSL_ASSERT_OK(destination.status());
GenerateManifestStoreRequest request;
request.mutable_actions()
->mutable_created()
->mutable_action_metadata()
->set_description("test only");
ABSL_ASSERT_OK(writer_->Write(request, &source, &destination));
ASSERT_TRUE(source.Close());
ASSERT_TRUE(destination.Close());
riegeli::StringReader result_reader(result);
ABSL_ASSERT_OK(result_reader.status());
EXPECT_THAT(Id3Extractor().ExtractManifestStoreLocation(result_reader, {}),
IsOkAndHolds(Optional(AllOf(Field(&ByteRange::offset, Gt(0)),
Field(&ByteRange::length, Gt(0))))));
ASSERT_TRUE(result_reader.Seek(0));
auto validation_result = validator_->Validate(result_reader, "audio/mpeg");
ABSL_ASSERT_OK(validation_result);
EXPECT_THAT(
(*validation_result)->proto().active_manifest().validation().failures(),
IsEmpty());
}
TEST_F(Id3WriterTest, WriteTwiceFails) {
GenerateManifestStoreRequest request;
request.mutable_actions()
->mutable_created()
->mutable_action_metadata()
->set_description("test only");
std::string mp3_file_contents =
std::string(kMp3FileWithNoId3Tag, sizeof(kMp3FileWithNoId3Tag));
riegeli::StringReader source1(mp3_file_contents);
ABSL_ASSERT_OK(source1.status());
std::string result1;
riegeli::StringWriter destination1(&result1);
ABSL_ASSERT_OK(destination1.status());
ABSL_ASSERT_OK(writer_->Write(request, &source1, &destination1));
ASSERT_TRUE(source1.Close());
ASSERT_TRUE(destination1.Close());
riegeli::StringReader source2(result1);
ABSL_ASSERT_OK(source2.status());
std::string result2;
riegeli::StringWriter destination2(&result2);
ABSL_ASSERT_OK(destination2.status());
EXPECT_THAT(
writer_->Write(request, &source2, &destination2),
StatusIs(absl::StatusCode::kAlreadyExists, HasSubstr("already exists")));
}
TEST_F(Id3WriterTest, WriteWithHardBindingFails) {
std::string mp3_file_contents =
std::string(kMp3FileWithNoId3Tag, sizeof(kMp3FileWithNoId3Tag));
riegeli::StringReader source(mp3_file_contents);
ABSL_ASSERT_OK(source.status());
std::string result;
riegeli::StringWriter destination(&result);
ABSL_ASSERT_OK(destination.status());
GenerateManifestStoreRequest request;
request.mutable_actions()
->mutable_created()
->mutable_action_metadata()
->set_description("test only");
request.mutable_hard_binding();
EXPECT_THAT(writer_->Write(request, &source, &destination),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("must not include `hard_binding`")));
}
class BadTemplateSizeGenerator : public ManifestStoreGenerator {
public:
void GenerateManifestStore(
GenerateManifestStoreRequest request, AsyncContext context,
absl::AnyInvocable<void(absl::StatusOr<GenerateManifestStoreResponse>) &&>
cb) const override {}
void GenerateManifestStoreTemplate(
GenerateManifestStoreRequest request, AsyncContext context,
absl::AnyInvocable<void(absl::StatusOr<ManifestStoreTemplate>) &&> cb)
const override {
ManifestStoreTemplate t;
t.set_manifest_store_size(-1);
std::move(cb)(t);
}
void FinalizeManifestStore(
FinalizeManifestStoreRequest request, AsyncContext context,
absl::AnyInvocable<void(absl::StatusOr<FinalizeManifestStoreResponse>) &&>
cb) const override {}
};
TEST_F(Id3WriterTest, WriteWithBadTemplateSizeFails) {
BadTemplateSizeGenerator bad_generator;
Id3Writer writer(&bad_generator);
std::string mp3_file_contents =
std::string(kMp3FileWithNoId3Tag, sizeof(kMp3FileWithNoId3Tag));
riegeli::StringReader source(mp3_file_contents);
std::string result;
riegeli::StringWriter destination(&result);
GenerateManifestStoreRequest request;
request.mutable_actions()
->mutable_created()
->mutable_action_metadata()
->set_description("test only");
EXPECT_THAT(writer.Write(request, &source, &destination),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("invalid manifest store template size")));
}
} // namespace
} // namespace credentio