blob: 494331075a314ff7486373bd4137f797248a52a8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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 <chrono>
#include <string>
#include <unordered_map>
#include "gtest/gtest.h"
#include "opendal.hpp"
class MetadataTest : public ::testing::Test {
protected:
opendal::Operator op;
std::string scheme = "memory";
std::unordered_map<std::string, std::string> config;
void SetUp() override {
op = opendal::Operator(scheme, config);
EXPECT_TRUE(op.Available());
}
// Helper methods for BDD-style testing
void given_a_file_exists_with_content(const std::string& path,
const std::string& content) {
op.Write(path, content);
EXPECT_TRUE(op.Exists(path));
}
void given_a_directory_exists(const std::string& path) {
op.CreateDir(path);
EXPECT_TRUE(op.Exists(path));
}
opendal::Metadata when_i_get_metadata_for(const std::string& path) {
return op.Stat(path);
}
void then_metadata_should_indicate_file_type(
const opendal::Metadata& metadata) {
EXPECT_TRUE(metadata.IsFile());
EXPECT_FALSE(metadata.IsDir());
EXPECT_EQ(metadata.Mode(), opendal::EntryMode::FILE);
EXPECT_EQ(metadata.type, opendal::EntryMode::FILE);
}
void then_metadata_should_indicate_directory_type(
const opendal::Metadata& metadata) {
EXPECT_FALSE(metadata.IsFile());
EXPECT_TRUE(metadata.IsDir());
EXPECT_EQ(metadata.Mode(), opendal::EntryMode::DIR);
EXPECT_EQ(metadata.type, opendal::EntryMode::DIR);
}
void then_content_length_should_be(const opendal::Metadata& metadata,
std::uint64_t expected_length) {
EXPECT_EQ(metadata.content_length, expected_length);
EXPECT_EQ(metadata.ContentLength(), expected_length);
}
void then_metadata_should_not_be_deleted(const opendal::Metadata& metadata) {
EXPECT_FALSE(metadata.is_deleted);
EXPECT_FALSE(metadata.IsDeleted());
}
};
// Feature: File Metadata Behavior
// As a developer using OpenDAL C++ bindings
// I want to retrieve accurate metadata for files
// So that I can understand file properties and make informed decisions
TEST_F(MetadataTest, BasicFileProperties) {
// Scenario: Getting metadata for a regular file
// Given a file exists with specific content
const std::string file_path = "test_file.txt";
const std::string file_content = "Hello, OpenDAL C++ bindings!";
given_a_file_exists_with_content(file_path, file_content);
// When I get metadata for the file
auto metadata = when_i_get_metadata_for(file_path);
// Then the metadata should indicate it's a file
then_metadata_should_indicate_file_type(metadata);
// And the content length should match the file content
then_content_length_should_be(metadata, file_content.length());
// And the file should not be marked as deleted
then_metadata_should_not_be_deleted(metadata);
}
// Feature: Directory Metadata Behavior
// As a developer using OpenDAL C++ bindings
// I want to retrieve accurate metadata for directories
// So that I can distinguish between files and directories
TEST_F(MetadataTest, BasicDirectoryProperties) {
// Scenario: Getting metadata for a directory
// Given a directory exists
const std::string dir_path = "test_directory/";
given_a_directory_exists(dir_path);
// When I get metadata for the directory
auto metadata = when_i_get_metadata_for(dir_path);
// Then the metadata should indicate it's a directory
then_metadata_should_indicate_directory_type(metadata);
// And the directory should not be marked as deleted
then_metadata_should_not_be_deleted(metadata);
}
TEST_F(MetadataTest, EmptyFile) {
// Scenario: Getting metadata for an empty file
// Given an empty file exists
const std::string file_path = "empty_file.txt";
const std::string empty_content = "";
given_a_file_exists_with_content(file_path, empty_content);
// When I get metadata for the empty file
auto metadata = when_i_get_metadata_for(file_path);
// Then the metadata should indicate it's a file
then_metadata_should_indicate_file_type(metadata);
// And the content length should be zero
then_content_length_should_be(metadata, 0);
// And the file should not be marked as deleted
then_metadata_should_not_be_deleted(metadata);
}
TEST_F(MetadataTest, LargeFile) {
// Scenario: Getting metadata for a large file
// Given a large file exists
const std::string file_path = "large_file.bin";
const std::size_t large_size = 1024 * 1024; // 1MB
const std::string large_content(large_size, 'X');
given_a_file_exists_with_content(file_path, large_content);
// When I get metadata for the large file
auto metadata = when_i_get_metadata_for(file_path);
// Then the metadata should indicate it's a file
then_metadata_should_indicate_file_type(metadata);
// And the content length should match the large size
then_content_length_should_be(metadata, large_size);
// And the file should not be marked as deleted
then_metadata_should_not_be_deleted(metadata);
}
TEST_F(MetadataTest, NestedDirectories) {
// Scenario: Getting metadata for nested directories
// Given nested directories exist
const std::string parent_dir = "parent/";
const std::string nested_dir = "parent/child/";
given_a_directory_exists(parent_dir);
given_a_directory_exists(nested_dir);
// When I get metadata for the parent directory
auto parent_metadata = when_i_get_metadata_for(parent_dir);
// And I get metadata for the nested directory
auto nested_metadata = when_i_get_metadata_for(nested_dir);
// Then both should indicate they are directories
then_metadata_should_indicate_directory_type(parent_metadata);
then_metadata_should_indicate_directory_type(nested_metadata);
// And neither should be marked as deleted
then_metadata_should_not_be_deleted(parent_metadata);
then_metadata_should_not_be_deleted(nested_metadata);
}
// Feature: HTTP Headers in Metadata
// As a developer using OpenDAL C++ bindings
// I want to access HTTP-style headers in metadata
// So that I can understand content properties and caching behavior
TEST_F(MetadataTest, OptionalFieldsAccessibility) {
// Scenario: Accessing optional HTTP header fields
// Given a file exists
const std::string file_path = "test_headers.txt";
const std::string content = "test content for headers";
given_a_file_exists_with_content(file_path, content);
// When I get metadata for the file
auto metadata = when_i_get_metadata_for(file_path);
// Then I should be able to access all optional header fields without crashes
EXPECT_NO_THROW(metadata.CacheControl());
EXPECT_NO_THROW(metadata.ContentDisposition());
EXPECT_NO_THROW(metadata.ContentMd5());
EXPECT_NO_THROW(metadata.ContentType());
EXPECT_NO_THROW(metadata.ContentEncoding());
EXPECT_NO_THROW(metadata.Etag());
EXPECT_NO_THROW(metadata.LastModified());
}
// Feature: Versioning Information in Metadata
// As a developer using OpenDAL C++ bindings
// I want to access versioning information in metadata
// So that I can work with versioned storage systems
TEST_F(MetadataTest, VersioningFields) {
// Scenario: Accessing versioning information
// Given a file exists
const std::string file_path = "versioned_file.txt";
const std::string content = "versioned content";
given_a_file_exists_with_content(file_path, content);
// When I get metadata for the file
auto metadata = when_i_get_metadata_for(file_path);
// Then I should be able to access versioning fields
EXPECT_NO_THROW(metadata.Version());
EXPECT_NO_THROW(metadata.IsCurrent());
EXPECT_NO_THROW(metadata.IsDeleted());
// And for memory storage, file should not be deleted by default
EXPECT_FALSE(metadata.IsDeleted());
}
// Feature: Metadata Consistency
// As a developer using OpenDAL C++ bindings
// I want consistent metadata behavior
// So that I can rely on the API behavior
TEST_F(MetadataTest, AccessorConsistency) {
// Scenario: Ensuring consistency between direct field access and accessor
// methods
// Given a file exists
const std::string file_path = "consistency_test.txt";
const std::string content = "test consistency";
given_a_file_exists_with_content(file_path, content);
// When I get metadata for the file
auto metadata = when_i_get_metadata_for(file_path);
// Then direct field access should match accessor methods
EXPECT_EQ(metadata.type, metadata.Mode());
EXPECT_EQ(metadata.content_length, metadata.ContentLength());
EXPECT_EQ(metadata.is_deleted, metadata.IsDeleted());
// And accessor methods should provide consistent boolean results
if (metadata.type == opendal::EntryMode::FILE) {
EXPECT_TRUE(metadata.IsFile());
EXPECT_FALSE(metadata.IsDir());
} else if (metadata.type == opendal::EntryMode::DIR) {
EXPECT_FALSE(metadata.IsFile());
EXPECT_TRUE(metadata.IsDir());
}
}
// Feature: Metadata Object Lifecycle
// As a developer using OpenDAL C++ bindings
// I want metadata objects to behave correctly throughout their lifecycle
// So that I can use them safely in my applications
TEST_F(MetadataTest, CopyAndMove) {
// Scenario: Copying and moving metadata objects
// Given a file exists
const std::string file_path = "lifecycle_test.txt";
const std::string content = "lifecycle test content";
given_a_file_exists_with_content(file_path, content);
// When I get metadata for the file
auto original_metadata = when_i_get_metadata_for(file_path);
// And I copy the metadata
auto copied_metadata = original_metadata;
// And I move the metadata
auto moved_metadata = std::move(original_metadata);
// Then the copied metadata should be identical to the moved one
EXPECT_EQ(copied_metadata.type, moved_metadata.type);
EXPECT_EQ(copied_metadata.content_length, moved_metadata.content_length);
EXPECT_EQ(copied_metadata.is_deleted, moved_metadata.is_deleted);
// And both should function correctly
EXPECT_EQ(copied_metadata.IsFile(), moved_metadata.IsFile());
EXPECT_EQ(copied_metadata.ContentLength(),
moved_metadata.ContentLength());
}
TEST_F(MetadataTest, DefaultConstruction) {
// Scenario: Default construction of metadata objects
// When I create a default metadata object
opendal::Metadata default_metadata;
// Then it should have sensible defaults
EXPECT_EQ(default_metadata.content_length, 0);
EXPECT_FALSE(default_metadata.is_deleted);
EXPECT_EQ(default_metadata.ContentLength(), 0);
EXPECT_FALSE(default_metadata.IsDeleted());
// And accessor methods should work without crashing
EXPECT_NO_THROW(default_metadata.Mode());
EXPECT_NO_THROW(default_metadata.IsFile());
EXPECT_NO_THROW(default_metadata.IsDir());
}
// Feature: Multiple Files Metadata Comparison
// As a developer using OpenDAL C++ bindings
// I want to compare metadata from different files
// So that I can make decisions based on file properties
TEST_F(MetadataTest, DifferentFileSizes) {
// Scenario: Comparing metadata from files of different sizes
// Given files of different sizes exist
const std::string small_file = "small.txt";
const std::string large_file = "large.txt";
const std::string small_content = "small";
const std::string large_content(1000, 'L');
given_a_file_exists_with_content(small_file, small_content);
given_a_file_exists_with_content(large_file, large_content);
// When I get metadata for both files
auto small_metadata = when_i_get_metadata_for(small_file);
auto large_metadata = when_i_get_metadata_for(large_file);
// Then both should be files
then_metadata_should_indicate_file_type(small_metadata);
then_metadata_should_indicate_file_type(large_metadata);
// And their sizes should differ appropriately
EXPECT_LT(small_metadata.ContentLength(),
large_metadata.ContentLength());
EXPECT_EQ(small_metadata.ContentLength(), small_content.length());
EXPECT_EQ(large_metadata.ContentLength(), large_content.length());
}
TEST_F(MetadataTest, FileVsDirectory) {
// Scenario: Comparing metadata between file and directory
// Given a file and directory exist
const std::string file_path = "comparison_file.txt";
const std::string dir_path = "comparison_dir/";
const std::string content = "file content";
given_a_file_exists_with_content(file_path, content);
given_a_directory_exists(dir_path);
// When I get metadata for both
auto file_metadata = when_i_get_metadata_for(file_path);
auto dir_metadata = when_i_get_metadata_for(dir_path);
// Then they should have different types
EXPECT_NE(file_metadata.type, dir_metadata.type);
EXPECT_TRUE(file_metadata.IsFile());
EXPECT_TRUE(dir_metadata.IsDir());
EXPECT_FALSE(file_metadata.IsDir());
EXPECT_FALSE(dir_metadata.IsFile());
// And both should not be deleted
then_metadata_should_not_be_deleted(file_metadata);
then_metadata_should_not_be_deleted(dir_metadata);
}
// Feature: Timestamp Behavior in Metadata
// As a developer using OpenDAL C++ bindings
// I want to access and understand timestamp information
// So that I can track file modification times
TEST_F(MetadataTest, LastModifiedAccess) {
// Scenario: Accessing last modified timestamp
// Given a file exists
const std::string file_path = "timestamp_test.txt";
const std::string content = "timestamp test content";
given_a_file_exists_with_content(file_path, content);
// When I get metadata for the file
auto metadata = when_i_get_metadata_for(file_path);
// Then I should be able to access the last modified timestamp
EXPECT_NO_THROW(metadata.LastModified());
// Note: For memory storage, last_modified might not be set
// This is expected behavior and not an error
}
TEST_F(MetadataTest, TimestampConsistency) {
// Scenario: Timestamp consistency across multiple file operations
// Given multiple files are created
const std::string file1 = "timestamp1.txt";
const std::string file2 = "timestamp2.txt";
const std::string content1 = "first file";
const std::string content2 = "second file";
given_a_file_exists_with_content(file1, content1);
given_a_file_exists_with_content(file2, content2);
// When I get metadata for both files
auto metadata1 = when_i_get_metadata_for(file1);
auto metadata2 = when_i_get_metadata_for(file2);
// Then both should handle timestamps consistently
EXPECT_NO_THROW(metadata1.LastModified());
EXPECT_NO_THROW(metadata2.LastModified());
// And the timestamp handling should be the same type
auto ts1 = metadata1.LastModified();
auto ts2 = metadata2.LastModified();
// Both should have the same availability (either both have timestamps or both
// don't)
EXPECT_EQ(ts1.has_value(), ts2.has_value());
}
// Feature: Error Handling and Edge Cases
// As a developer using OpenDAL C++ bindings
// I want the metadata API to handle edge cases gracefully
// So that my application doesn't crash on unexpected inputs
TEST_F(MetadataTest, UnknownEntryMode) {
// Scenario: Handling unknown entry modes gracefully
// Given a metadata object with unknown mode (using default constructor)
opendal::Metadata unknown_metadata;
unknown_metadata.type = opendal::EntryMode::UNKNOWN;
// When I check the type using accessor methods
auto mode = unknown_metadata.Mode();
auto is_file = unknown_metadata.IsFile();
auto is_dir = unknown_metadata.IsDir();
// Then it should handle unknown type without crashing
EXPECT_EQ(mode, opendal::EntryMode::UNKNOWN);
EXPECT_FALSE(is_file); // Unknown is not a file
EXPECT_FALSE(is_dir); // Unknown is not a directory
}
TEST_F(MetadataTest, EmptyOptionalFields) {
// Scenario: Accessing optional fields when they're empty
// Given a default metadata object (which should have empty optional fields)
opendal::Metadata empty_metadata;
// When I access all optional fields
auto cache_control = empty_metadata.CacheControl();
auto content_disposition = empty_metadata.ContentDisposition();
auto content_md5 = empty_metadata.ContentMd5();
auto content_type = empty_metadata.ContentType();
auto content_encoding = empty_metadata.ContentEncoding();
auto etag = empty_metadata.Etag();
auto version = empty_metadata.Version();
auto is_current = empty_metadata.IsCurrent();
auto last_modified = empty_metadata.LastModified();
// Then all optional fields should be empty (no value)
EXPECT_FALSE(cache_control.has_value());
EXPECT_FALSE(content_disposition.has_value());
EXPECT_FALSE(content_md5.has_value());
EXPECT_FALSE(content_type.has_value());
EXPECT_FALSE(content_encoding.has_value());
EXPECT_FALSE(etag.has_value());
EXPECT_FALSE(version.has_value());
EXPECT_FALSE(is_current.has_value());
EXPECT_FALSE(last_modified.has_value());
// And accessing them should not crash
EXPECT_NO_THROW(empty_metadata.CacheControl());
EXPECT_NO_THROW(empty_metadata.ContentDisposition());
EXPECT_NO_THROW(empty_metadata.ContentMd5());
EXPECT_NO_THROW(empty_metadata.ContentType());
EXPECT_NO_THROW(empty_metadata.ContentEncoding());
EXPECT_NO_THROW(empty_metadata.Etag());
EXPECT_NO_THROW(empty_metadata.Version());
EXPECT_NO_THROW(empty_metadata.IsCurrent());
EXPECT_NO_THROW(empty_metadata.LastModified());
}
TEST_F(MetadataTest, LongFilenames) {
// Scenario: Handling metadata for files with very long names
// Given a file with a very long name exists
const std::string long_filename = std::string(200, 'a') + ".txt";
const std::string content = "content for long filename";
given_a_file_exists_with_content(long_filename, content);
// When I get metadata for the file with long name
auto metadata = when_i_get_metadata_for(long_filename);
// Then the metadata should be retrieved successfully
then_metadata_should_indicate_file_type(metadata);
then_content_length_should_be(metadata, content.length());
then_metadata_should_not_be_deleted(metadata);
}
TEST_F(MetadataTest, SpecialCharactersInFilenames) {
// Scenario: Handling metadata for files with special characters
// Given files with special characters exist
const std::string special_file1 = "file-with-dashes.txt";
const std::string special_file2 = "file_with_underscores.txt";
const std::string special_file3 = "file.with.dots.txt";
const std::string content = "special chars content";
given_a_file_exists_with_content(special_file1, content);
given_a_file_exists_with_content(special_file2, content);
given_a_file_exists_with_content(special_file3, content);
// When I get metadata for files with special characters
auto metadata1 = when_i_get_metadata_for(special_file1);
auto metadata2 = when_i_get_metadata_for(special_file2);
auto metadata3 = when_i_get_metadata_for(special_file3);
// Then all should be recognized as files with correct properties
then_metadata_should_indicate_file_type(metadata1);
then_metadata_should_indicate_file_type(metadata2);
then_metadata_should_indicate_file_type(metadata3);
then_content_length_should_be(metadata1, content.length());
then_content_length_should_be(metadata2, content.length());
then_content_length_should_be(metadata3, content.length());
}
// Feature: Metadata API Robustness
// As a developer using OpenDAL C++ bindings
// I want the metadata API to be robust and predictable
// So that I can build reliable applications
TEST_F(MetadataTest, RepeatedAccess) {
// Scenario: Retrieving metadata multiple times for the same file
// Given a file exists
const std::string file_path = "repeated_access.txt";
const std::string content = "repeated access content";
given_a_file_exists_with_content(file_path, content);
// When I get metadata multiple times
auto metadata1 = when_i_get_metadata_for(file_path);
auto metadata2 = when_i_get_metadata_for(file_path);
auto metadata3 = when_i_get_metadata_for(file_path);
// Then all metadata objects should be consistent
EXPECT_EQ(metadata1.type, metadata2.type);
EXPECT_EQ(metadata2.type, metadata3.type);
EXPECT_EQ(metadata1.content_length, metadata2.content_length);
EXPECT_EQ(metadata2.content_length, metadata3.content_length);
EXPECT_EQ(metadata1.is_deleted, metadata2.is_deleted);
EXPECT_EQ(metadata2.is_deleted, metadata3.is_deleted);
// And all should behave identically
EXPECT_EQ(metadata1.IsFile(), metadata2.IsFile());
EXPECT_EQ(metadata2.IsFile(), metadata3.IsFile());
EXPECT_EQ(metadata1.ContentLength(), metadata2.ContentLength());
EXPECT_EQ(metadata2.ContentLength(), metadata3.ContentLength());
}
TEST_F(MetadataTest, AfterFileModification) {
// Scenario: Getting metadata after modifying a file
// Given a file exists with initial content
const std::string file_path = "modifiable_file.txt";
const std::string initial_content = "initial content";
const std::string modified_content = "modified content with more text";
given_a_file_exists_with_content(file_path, initial_content);
// When I get initial metadata
auto initial_metadata = when_i_get_metadata_for(file_path);
// And I modify the file content
op.Write(file_path, modified_content);
// And I get metadata again
auto modified_metadata = when_i_get_metadata_for(file_path);
// Then both should indicate file type
then_metadata_should_indicate_file_type(initial_metadata);
then_metadata_should_indicate_file_type(modified_metadata);
// And the content length should reflect the change
then_content_length_should_be(initial_metadata, initial_content.length());
then_content_length_should_be(modified_metadata, modified_content.length());
// And the new content length should be different from the old one
EXPECT_NE(initial_metadata.ContentLength(),
modified_metadata.ContentLength());
EXPECT_GT(modified_metadata.ContentLength(),
initial_metadata.ContentLength());
}