* Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
*
* 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
*
* 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.
*/
* Description: OBS V2 signature utility unit test.
*/
#include <gtest/gtest.h>
#include <map>
#include <string>
#include "datasystem/common/l2cache/obs_client/obs_signature.h"
namespace datasystem {
namespace ut {
TEST(ObsSignatureTest, BuildStringToSignBasicFields)
{
std::map<std::string, std::string> headers;
std::string result = ObsSignature::BuildStringToSign("PUT", "d41d8cd98f00b204e9800998ecf8427e",
"text/plain", "Wed, 01 Mar 2024 12:00:00 GMT", headers,
"/bucket/object");
EXPECT_EQ(result,
"PUT\nd41d8cd98f00b204e9800998ecf8427e\ntext/plain\nWed, 01 Mar 2024 12:00:00 GMT\n/bucket/object");
}
TEST(ObsSignatureTest, BuildStringToSignEmptyFields)
{
std::map<std::string, std::string> headers;
std::string result =
ObsSignature::BuildStringToSign("GET", "", "", "Wed, 01 Mar 2024 12:00:00 GMT", headers, "/bucket/object");
EXPECT_EQ(result, "GET\n\n\nWed, 01 Mar 2024 12:00:00 GMT\n/bucket/object");
}
TEST(ObsSignatureTest, BuildStringToSignWithObsHeaders)
{
std::map<std::string, std::string> headers;
headers["x-obs-acl"] = "public-read";
headers["x-obs-meta-key"] = "value";
std::string result = ObsSignature::BuildStringToSign("PUT", "", "", "Wed, 01 Mar 2024 12:00:00 GMT", headers,
"/bucket/object");
EXPECT_NE(result.find("x-obs-acl:public-read"), std::string::npos);
EXPECT_NE(result.find("x-obs-meta-key:value"), std::string::npos);
EXPECT_EQ(result.substr(0, 4), "PUT\n");
EXPECT_GE(result.find("/bucket/object"), 0u);
}
TEST(ObsSignatureTest, BuildStringToSignNoHeaders)
{
std::map<std::string, std::string> headers;
std::string result =
ObsSignature::BuildStringToSign("GET", "", "", "Wed, 01 Mar 2024 12:00:00 GMT", headers, "/bucket/");
EXPECT_EQ(result, "GET\n\n\nWed, 01 Mar 2024 12:00:00 GMT\n/bucket/");
}
TEST(ObsSignatureTest, SignProducesValidBase64)
{
std::string signature;
std::string stringToSign = "GET\n\n\nWed, 01 Mar 2024 12:00:00 GMT\n/bucket/object";
Status rc = ObsSignature::Sign("my-secret-key", stringToSign, signature);
ASSERT_EQ(rc.GetCode(), 0);
EXPECT_FALSE(signature.empty());
for (char c : signature) {
EXPECT_TRUE((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '+'
|| c == '/' || c == '=');
}
}
TEST(ObsSignatureTest, SignDeterministic)
{
std::string stringToSign = "PUT\n\napplication/xml\nWed, 01 Mar 2024 12:00:00 GMT\n/bucket/object";
std::string sig1, sig2;
ASSERT_EQ(ObsSignature::Sign("secret", stringToSign, sig1).GetCode(), 0);
ASSERT_EQ(ObsSignature::Sign("secret", stringToSign, sig2).GetCode(), 0);
EXPECT_EQ(sig1, sig2);
}
TEST(ObsSignatureTest, SignDifferentKeys)
{
std::string stringToSign = "GET\n\n\nWed, 01 Mar 2024 12:00:00 GMT\n/bucket/object";
std::string sig1, sig2;
ASSERT_EQ(ObsSignature::Sign("secret-a", stringToSign, sig1).GetCode(), 0);
ASSERT_EQ(ObsSignature::Sign("secret-b", stringToSign, sig2).GetCode(), 0);
EXPECT_NE(sig1, sig2);
}
TEST(ObsSignatureTest, BuildAuthHeaderFormat)
{
std::string result = ObsSignature::BuildAuthHeader("myAccessKey", "abc123signature==");
EXPECT_EQ(result, "OBS myAccessKey:abc123signature==");
}
TEST(ObsSignatureTest, BuildAuthHeaderEmptySignature)
{
std::string result = ObsSignature::BuildAuthHeader("myAccessKey", "");
EXPECT_EQ(result, "OBS myAccessKey:");
}
TEST(ObsSignatureTest, BuildAuthHeaderContainsOBS)
{
std::string result = ObsSignature::BuildAuthHeader("AK123", "sig456");
EXPECT_EQ(result.substr(0, 4), "OBS ");
EXPECT_NE(result.find("AK123:"), std::string::npos);
}
TEST(ObsSignatureTest, BuildCanonicalResourceBucketOnly)
{
std::string result = ObsSignature::BuildCanonicalResource("my-bucket", "");
EXPECT_EQ(result, "/my-bucket/");
}
TEST(ObsSignatureTest, BuildCanonicalResourceWithObjectKey)
{
std::string result = ObsSignature::BuildCanonicalResource("my-bucket", "path/to/object.txt");
EXPECT_EQ(result, "/my-bucket/path/to/object.txt");
}
TEST(ObsSignatureTest, BuildCanonicalResourceWithSubResources)
{
std::map<std::string, std::string> subResources;
subResources["uploads"] = "";
std::string result = ObsSignature::BuildCanonicalResource("my-bucket", "obj", subResources);
EXPECT_NE(result.find("?uploads"), std::string::npos);
}
TEST(ObsSignatureTest, BuildCanonicalResourceWithSubResourcesWithValues)
{
std::map<std::string, std::string> subResources;
subResources["uploadId"] = "upload123";
subResources["partNumber"] = "1";
std::string result = ObsSignature::BuildCanonicalResource("my-bucket", "obj", subResources);
EXPECT_NE(result.find("uploadId=upload123"), std::string::npos);
EXPECT_NE(result.find("partNumber=1"), std::string::npos);
}
TEST(ObsSignatureTest, BuildCanonicalResourceNoSubResources)
{
std::string result = ObsSignature::BuildCanonicalResource("bucket", "key");
EXPECT_EQ(result, "/bucket/key");
}
TEST(ObsSignatureTest, FormatDateRFC1123EndsWithGMT)
{
std::string date = ObsSignature::FormatDateRFC1123();
EXPECT_GE(date.size(), 24u);
EXPECT_EQ(date.substr(date.size() - 3), "GMT");
}
TEST(ObsSignatureTest, FormatDateRFC1123ContainsComma)
{
std::string date = ObsSignature::FormatDateRFC1123();
EXPECT_NE(date.find(","), std::string::npos);
}
TEST(ObsSignatureTest, FormatDateRFC1123ContainsColons)
{
std::string date = ObsSignature::FormatDateRFC1123();
EXPECT_NE(date.find(":"), std::string::npos);
}
}
}