blob: 52b2f0da639e7a394f2776803d17b886d108578c [file]
/*
* 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 "iceberg/catalog/rest/error_handlers.h"
#include <string_view>
#include <gtest/gtest.h>
#include "iceberg/catalog/rest/types.h"
#include "iceberg/test/matchers.h"
namespace iceberg::rest {
namespace {
void ExpectErrorWithMessage(const Status& status, ErrorKind kind,
std::string_view message) {
ASSERT_FALSE(status.has_value());
EXPECT_EQ(status.error().kind, kind);
EXPECT_EQ(status.error().message, message);
}
} // namespace
TEST(ErrorHandlersTest, DefaultErrorHandlerIncludesCodeAndType) {
ErrorResponse error{
.code = 422,
.type = "ValidationException",
.message = "Invalid input",
};
ExpectErrorWithMessage(
DefaultErrorHandler::Instance()->Accept(error), ErrorKind::kRestError,
"Unable to process (code: 422, type: ValidationException): Invalid input");
}
TEST(ErrorHandlersTest, DefaultErrorHandlerWithCodeOnly) {
ErrorResponse error{
.code = 422,
.type = "",
.message = "",
};
ExpectErrorWithMessage(DefaultErrorHandler::Instance()->Accept(error),
ErrorKind::kRestError,
"Unable to process (code: 422, type: null): null");
}
TEST(ErrorHandlersTest, DefaultErrorHandlerWithCodeAndMessageOnly) {
ErrorResponse error{
.code = 422,
.type = "",
.message = "Invalid input",
};
ExpectErrorWithMessage(DefaultErrorHandler::Instance()->Accept(error),
ErrorKind::kRestError,
"Unable to process (code: 422, type: null): Invalid input");
}
TEST(ErrorHandlersTest, DefaultErrorHandlerWithCodeAndTypeOnly) {
ErrorResponse error{
.code = 422,
.type = "ValidationException",
.message = "",
};
ExpectErrorWithMessage(
DefaultErrorHandler::Instance()->Accept(error), ErrorKind::kRestError,
"Unable to process (code: 422, type: ValidationException): null");
}
TEST(ErrorHandlersTest, NamespaceErrorHandlerFormats422AsRestError) {
ErrorResponse error{
.code = 422,
.type = "ValidationException",
.message = "Invalid namespace",
};
ExpectErrorWithMessage(
NamespaceErrorHandler::Instance()->Accept(error), ErrorKind::kRestError,
"Unable to process (code: 422, type: ValidationException): Invalid namespace");
}
TEST(ErrorHandlersTest, TableErrorHandlerMaps404NotFoundToNotFound) {
ErrorResponse error{
.code = 404,
.type = "NotFoundException",
.message = "Failed to open input stream for file: metadata.json",
};
EXPECT_THAT(TableErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kNotFound));
EXPECT_THAT(TableErrorHandler::Instance()->Accept(error),
HasErrorMessage("metadata.json"));
}
TEST(ErrorHandlersTest, TableErrorHandlerMaps404ToNoSuchTableByDefault) {
ErrorResponse error{
.code = 404,
.type = "NoSuchTableException",
.message = "Table does not exist",
};
EXPECT_THAT(TableErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kNoSuchTable));
}
TEST(ErrorHandlersTest, CreateTableErrorHandlerMaps404ToNoSuchNamespace) {
ErrorResponse error{
.code = 404,
.type = "NoSuchNamespaceException",
.message = "Namespace does not exist",
};
EXPECT_THAT(CreateTableErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kNoSuchNamespace));
}
TEST(ErrorHandlersTest, CreateTableErrorHandlerMaps409ToAlreadyExists) {
ErrorResponse error{
.code = 409,
.type = "AlreadyExistsException",
.message = "Table already exists",
};
EXPECT_THAT(CreateTableErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kAlreadyExists));
}
TEST(ErrorHandlersTest, CreateTableErrorHandlerMapsServiceFailureToCommitStateUnknown) {
ErrorResponse error{
.code = 503,
.type = "ServiceFailureException",
.message = "Service unavailable",
};
EXPECT_THAT(CreateTableErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kCommitStateUnknown));
EXPECT_THAT(CreateTableErrorHandler::Instance()->Accept(error),
HasErrorMessage("Service failed: 503: Service unavailable"));
}
TEST(ErrorHandlersTest, PlanErrorHandlerMapsUnknown404ToNoSuchPlanId) {
ErrorResponse error{
.code = 404,
.type = "UnknownException",
.message = "Plan does not exist",
};
EXPECT_THAT(PlanErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kNoSuchPlanId));
}
TEST(ErrorHandlersTest, PlanErrorHandlerDelegates406ToDefaultHandler) {
ErrorResponse error{
.code = 406,
.type = "NotAcceptableException",
.message = "Not acceptable",
};
ExpectErrorWithMessage(
PlanErrorHandler::Instance()->Accept(error), ErrorKind::kRestError,
"Unable to process (code: 406, type: NotAcceptableException): Not acceptable");
}
TEST(ErrorHandlersTest, PlanTaskErrorHandlerMapsUnknown404ToNoSuchPlanTask) {
ErrorResponse error{
.code = 404,
.type = "UnknownException",
.message = "Plan task does not exist",
};
EXPECT_THAT(PlanTaskErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kNoSuchPlanTask));
}
TEST(ErrorHandlersTest, OAuthErrorHandlerMapsInvalidClientToNotAuthorized) {
ErrorResponse error{
.code = 400,
.type = "invalid_client",
.message = "Credentials given were invalid",
};
EXPECT_THAT(OAuthErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kNotAuthorized));
EXPECT_THAT(
OAuthErrorHandler::Instance()->Accept(error),
HasErrorMessage("Not authorized: invalid_client: Credentials given were invalid"));
}
TEST(ErrorHandlersTest, OAuthErrorHandlerParsesOAuthErrorResponse) {
auto parse_result = OAuthErrorHandler::Instance()->ParseResponse(
400,
R"({"error":"invalid_client","error_description":"Credentials given were invalid"})");
ASSERT_TRUE(parse_result.has_value());
EXPECT_EQ(parse_result->code, 400);
EXPECT_EQ(parse_result->type, "invalid_client");
EXPECT_EQ(parse_result->message, "Credentials given were invalid");
EXPECT_THAT(OAuthErrorHandler::Instance()->Accept(*parse_result),
IsError(ErrorKind::kNotAuthorized));
}
TEST(ErrorHandlersTest, OAuthErrorHandlerMapsClientErrorsToBadRequest) {
ErrorResponse error{
.code = 400,
.type = "invalid_grant",
.message = "Grant is invalid",
};
EXPECT_THAT(OAuthErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kBadRequest));
EXPECT_THAT(OAuthErrorHandler::Instance()->Accept(error),
HasErrorMessage("Malformed request: invalid_grant: Grant is invalid"));
}
TEST(ErrorHandlersTest, ConfigErrorHandlerMapsTyped404ToNoSuchWarehouse) {
ErrorResponse error{
.code = 404,
.type = "NotFoundException",
.message = "Warehouse not found",
};
EXPECT_THAT(ConfigErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kNoSuchWarehouse));
}
TEST(ErrorHandlersTest, ConfigErrorHandlerDelegatesUntyped404ToDefaultHandler) {
ErrorResponse error{
.code = 404,
.type = "",
.message = "Not Found",
};
EXPECT_THAT(ConfigErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kRestError));
EXPECT_THAT(ConfigErrorHandler::Instance()->Accept(error),
HasErrorMessage("Not Found"));
}
TEST(ErrorHandlersTest, ConfigErrorHandlerDelegatesFallback404ToDefaultHandler) {
ErrorResponse error{
.code = 404,
.type = "RESTException",
.message = "Not Found",
};
EXPECT_THAT(ConfigErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kRestError));
EXPECT_THAT(ConfigErrorHandler::Instance()->Accept(error),
HasErrorMessage("Not Found"));
}
TEST(ErrorHandlersTest, ConfigErrorHandlerDelegatesNon404ToDefaultHandler) {
ErrorResponse error{
.code = 500,
.type = "",
.message = "Internal server error",
};
EXPECT_THAT(ConfigErrorHandler::Instance()->Accept(error),
IsError(ErrorKind::kInternalServerError));
EXPECT_THAT(ConfigErrorHandler::Instance()->Accept(error),
HasErrorMessage("Internal server error"));
}
} // namespace iceberg::rest