blob: 96569603903424fdd43873dbfaa971efbf725bfc [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/pdf/writer.h"
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include "absl/log/absl_check.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/strings/string_view.h"
#include "formats/byte_range.h"
#include "formats/pdf/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/fake_manifest_store_generator.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 absl::string_view kTestPdfContent = R"(%PDF-1.7
%%%%%
1 0 obj
/test_name
endobj
2 0 obj
123.456
endobj
3 0 obj
<</Type /Catalog>>
endobj
xref
0 4
0000000000 65535 f
0000000015 00000 n
0000000041 00000 n
0000000064 00000 n
trailer
<</Root 3 0 R /Size 4>>
startxref
98
%%EOF
)";
class PdfWriterTest : public ::testing::Test {
protected:
void SetUp() override {
auto generator = CreateNonProdManifestStoreGenerator({});
ABSL_CHECK_OK(generator);
generator_ = std::move(*generator);
writer_ = std::make_unique<PdfWriter>(generator_.get());
auto validator = CreateNonProdValidator({});
ABSL_CHECK_OK(validator);
validator_ = std::move(*validator);
}
std::unique_ptr<ManifestStoreGenerator> generator_;
std::unique_ptr<PdfWriter> writer_;
std::unique_ptr<AssetValidator> validator_;
};
TEST_F(PdfWriterTest, Write) {
GenerateManifestStoreRequest request;
request.mutable_actions()
->mutable_created()
->mutable_action_metadata()
->set_description("test only");
riegeli::StringReader source(kTestPdfContent);
ABSL_ASSERT_OK(source.status());
std::string result;
riegeli::StringWriter destination(&result);
ABSL_ASSERT_OK(destination.status());
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(PdfExtractor().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, "application/pdf");
ABSL_ASSERT_OK(validation_result);
EXPECT_THAT(
(*validation_result)->proto().active_manifest().validation().failures(),
IsEmpty());
}
TEST_F(PdfWriterTest, WriteTwiceFails) {
GenerateManifestStoreRequest request;
request.mutable_actions()
->mutable_created()
->mutable_action_metadata()
->set_description("test only");
riegeli::StringReader source1(kTestPdfContent);
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(PdfWriterTest, WriteWithHardBindingFails) {
GenerateManifestStoreRequest request;
request.mutable_actions()
->mutable_created()
->mutable_action_metadata()
->set_description("test only");
request.mutable_hard_binding();
riegeli::StringReader source(kTestPdfContent);
ABSL_ASSERT_OK(source.status());
std::string result;
riegeli::StringWriter destination(&result);
ABSL_ASSERT_OK(destination.status());
EXPECT_THAT(writer_->Write(request, &source, &destination),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("must not include `hard_binding`")));
}
class PdfWriterInvalidSizeTest : public ::testing::TestWithParam<int64_t> {};
TEST_P(PdfWriterInvalidSizeTest, RejectsInvalidManifestStoreSize) {
int64_t invalid_size = GetParam();
FakeManifestStoreGenerator generator;
generator.mutable_manifest_store_template()->set_manifest_store_size(
invalid_size);
PdfWriter writer(&generator);
GenerateManifestStoreRequest request;
request.mutable_actions()
->mutable_created()
->mutable_action_metadata()
->set_description("test only");
riegeli::StringReader source(kTestPdfContent);
ABSL_ASSERT_OK(source.status());
std::string result;
riegeli::StringWriter destination(&result);
ABSL_ASSERT_OK(destination.status());
EXPECT_THAT(writer.Write(request, &source, &destination),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("invalid manifest store template size:")));
}
INSTANTIATE_TEST_SUITE_P(PdfWriterInvalidSizeTests, PdfWriterInvalidSizeTest,
::testing::Values(-5, 0, 100LL * 1024 * 1024 + 1));
} // namespace
} // namespace credentio