Adds Protobuf support to Client<>. (#34505)
This commit is contained in:
@@ -160,10 +160,10 @@ void BraveAccountService::RegisterInitialize(
|
||||
}
|
||||
|
||||
auto request = MakeRequest<PasswordInit::Request>();
|
||||
request.blinded_message = blinded_message;
|
||||
request.initiating_service_name = "accounts";
|
||||
request.new_account_email = email;
|
||||
request.serialize_response = true;
|
||||
request.body.blinded_message = blinded_message;
|
||||
request.body.initiating_service_name = "accounts";
|
||||
request.body.new_account_email = email;
|
||||
request.body.serialize_response = true;
|
||||
Client<PasswordInit>::Send(
|
||||
url_loader_factory_, std::move(request),
|
||||
base::BindOnce(&BraveAccountService::OnRegisterInitialize,
|
||||
@@ -188,7 +188,7 @@ void BraveAccountService::RegisterFinalize(
|
||||
|
||||
auto request = MakeRequest<WithHeaders<PasswordFinalize::Request>>();
|
||||
SetBearerToken(request, verification_token);
|
||||
request.serialized_record = serialized_record;
|
||||
request.body.serialized_record = serialized_record;
|
||||
Client<PasswordFinalize>::Send(
|
||||
url_loader_factory_, std::move(request),
|
||||
base::BindOnce(&BraveAccountService::OnRegisterFinalize,
|
||||
@@ -219,7 +219,7 @@ void BraveAccountService::ResendConfirmationEmail(
|
||||
SetBearerToken(request, verification_token);
|
||||
// Server side will determine locale based on the Accept-Language request
|
||||
// header (which is included automatically by upstream).
|
||||
request.locale = "";
|
||||
request.body.locale = "";
|
||||
request.timeout_duration = kVerifyResendTimeout;
|
||||
Client<VerifyResend>::Send(
|
||||
url_loader_factory_, std::move(request),
|
||||
@@ -254,9 +254,9 @@ void BraveAccountService::LoginInitialize(const std::string& email,
|
||||
}
|
||||
|
||||
auto request = MakeRequest<LoginInit::Request>();
|
||||
request.email = email;
|
||||
request.initiating_service_name = "accounts";
|
||||
request.serialized_ke1 = serialized_ke1;
|
||||
request.body.email = email;
|
||||
request.body.initiating_service_name = "accounts";
|
||||
request.body.serialized_ke1 = serialized_ke1;
|
||||
Client<LoginInit>::Send(
|
||||
url_loader_factory_, std::move(request),
|
||||
base::BindOnce(&BraveAccountService::OnLoginInitialize,
|
||||
@@ -279,7 +279,7 @@ void BraveAccountService::LoginFinalize(
|
||||
|
||||
auto request = MakeRequest<WithHeaders<LoginFinalize::Request>>();
|
||||
SetBearerToken(request, login_token);
|
||||
request.client_mac = client_mac;
|
||||
request.body.client_mac = client_mac;
|
||||
Client<endpoints::LoginFinalize>::Send(
|
||||
url_loader_factory_, std::move(request),
|
||||
base::BindOnce(&BraveAccountService::OnLoginFinalize,
|
||||
@@ -343,7 +343,7 @@ void BraveAccountService::GetServiceToken(mojom::Service service,
|
||||
|
||||
auto request = MakeRequest<WithHeaders<ServiceToken::Request>>();
|
||||
SetBearerToken(request, authentication_token);
|
||||
request.service = service_name;
|
||||
request.body.service = service_name;
|
||||
Client<ServiceToken>::Send(
|
||||
url_loader_factory_, std::move(request),
|
||||
base::BindOnce(&BraveAccountService::OnGetServiceToken,
|
||||
@@ -497,7 +497,7 @@ void BraveAccountService::VerifyResult(
|
||||
|
||||
auto request = MakeRequest<WithHeaders<VerifyResult::Request>>();
|
||||
SetBearerToken(request, verification_token);
|
||||
request.wait = false;
|
||||
request.body.wait = false;
|
||||
current_verify_result_request =
|
||||
Client<endpoints::VerifyResult>::Send<RequestCancelability::kCancelable>(
|
||||
url_loader_factory_, std::move(request),
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
import("//third_party/protobuf/proto_library.gni")
|
||||
import("//tools/json_schema_compiler/json_schema_api.gni")
|
||||
|
||||
source_set("endpoint_client") {
|
||||
sources = [
|
||||
"brave_endpoint.h",
|
||||
@@ -27,8 +30,26 @@ source_set("endpoint_client") {
|
||||
"//brave/brave_domains",
|
||||
"//net",
|
||||
"//services/network/public/cpp",
|
||||
"//third_party/protobuf:protobuf_lite",
|
||||
"//url",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
"//brave/components/brave_account/endpoint_client:json_empty_body",
|
||||
"//brave/components/brave_account/endpoint_client:protobuf_empty_body",
|
||||
]
|
||||
}
|
||||
|
||||
generated_types("json_empty_body") {
|
||||
sources = [ "json_empty_body.idl" ]
|
||||
|
||||
deps = [ "//base" ]
|
||||
|
||||
root_namespace = "brave_account::%(namespace)s"
|
||||
}
|
||||
|
||||
proto_library("protobuf_empty_body") {
|
||||
sources = [ "protobuf_empty_body.proto" ]
|
||||
}
|
||||
|
||||
source_set("test_support") {
|
||||
@@ -50,7 +71,6 @@ source_set("unit_tests") {
|
||||
|
||||
sources = [
|
||||
"client_unittest.cc",
|
||||
"concept_test.h",
|
||||
"is_endpoint_unittest.cc",
|
||||
"is_request_body_unittest.cc",
|
||||
"is_request_unittest.cc",
|
||||
@@ -63,11 +83,30 @@ source_set("unit_tests") {
|
||||
"//base",
|
||||
"//base/test:test_support",
|
||||
"//brave/components/brave_account/endpoint_client",
|
||||
"//brave/components/brave_account/endpoint_client:json_test_endpoint_bodies",
|
||||
"//brave/components/brave_account/endpoint_client:protobuf_test_endpoint_bodies",
|
||||
"//net",
|
||||
"//services/network:test_support",
|
||||
"//services/network/public/cpp",
|
||||
"//services/network/public/mojom",
|
||||
"//testing/gtest",
|
||||
"//third_party/protobuf:protobuf_lite",
|
||||
"//url",
|
||||
]
|
||||
}
|
||||
|
||||
generated_types("json_test_endpoint_bodies") {
|
||||
# testonly = true
|
||||
|
||||
sources = [ "json_test_endpoint_bodies.idl" ]
|
||||
|
||||
deps = [ "//base" ]
|
||||
|
||||
root_namespace = "brave_account::%(namespace)s"
|
||||
}
|
||||
|
||||
proto_library("protobuf_test_endpoint_bodies") {
|
||||
testonly = true
|
||||
|
||||
sources = [ "protobuf_test_endpoint_bodies.proto" ]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
include_rules = [
|
||||
"+google/protobuf",
|
||||
"+services/network/public/cpp",
|
||||
"+services/network/public/mojom",
|
||||
"+services/network/test",
|
||||
]
|
||||
@@ -18,30 +18,21 @@
|
||||
#include "base/check_is_test.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/json/json_reader.h"
|
||||
#include "base/json/json_writer.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/types/expected.h"
|
||||
#include "base/types/is_instantiation.h"
|
||||
#include "base/values.h"
|
||||
#include "base/types/to_address.h"
|
||||
#include "brave/components/brave_account/endpoint_client/is_endpoint.h"
|
||||
#include "brave/components/brave_account/endpoint_client/maybe_strip_with_headers.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_handle.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "brave/components/brave_account/endpoint_client/with_headers.h"
|
||||
#include "net/base/load_flags.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/http/http_request_headers.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "net/traffic_annotation/network_traffic_annotation.h"
|
||||
#include "services/network/public/cpp/header_util.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "services/network/public/cpp/shared_url_loader_factory.h"
|
||||
#include "services/network/public/cpp/simple_url_loader.h"
|
||||
#include "services/network/public/mojom/fetch_api.mojom.h"
|
||||
#include "services/network/public/mojom/url_response_head.mojom.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
// See //brave/components/brave_account/endpoint_client/README.md
|
||||
// for design, motivation, usage, and examples.
|
||||
@@ -50,8 +41,92 @@ namespace brave_account::endpoint_client {
|
||||
|
||||
enum class RequestCancelability { kNonCancelable, kCancelable };
|
||||
|
||||
template <IsEndpoint T>
|
||||
// Client<> performs a full network roundtrip for a given endpoint using
|
||||
// network::SimpleURLLoader. It serializes the endpoint's Request type,
|
||||
// performs the network call, and invokes a callback with the response
|
||||
// deserialized as the endpoint's Response type. Requests and responses may
|
||||
// optionally be wrapped in WithHeaders<> to include HTTP headers. Requests
|
||||
// can also be made cancelable, in which case Send<>() returns a RequestHandle
|
||||
// that can cancel the request.
|
||||
template <IsEndpoint Endpoint>
|
||||
class Client {
|
||||
public:
|
||||
// [[nodiscard]] enforces that callers retain the returned handle in
|
||||
// the cancelable case. It has no effect in the non-cancelable case,
|
||||
// where the return type is void.
|
||||
template <RequestCancelability C = RequestCancelability::kNonCancelable,
|
||||
typename Request,
|
||||
typename Response>
|
||||
requires(std::same_as<detail::MaybeStripWithHeaders<Request>,
|
||||
typename Endpoint::Request> &&
|
||||
std::same_as<detail::MaybeStripWithHeaders<Response>,
|
||||
typename Endpoint::Response>)
|
||||
[[nodiscard]] static auto Send(
|
||||
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
|
||||
Request request,
|
||||
base::OnceCallback<void(Response)> callback) {
|
||||
CHECK(url_loader_factory);
|
||||
if (!request.network_traffic_annotation_tag.is_valid()) {
|
||||
CHECK_IS_TEST()
|
||||
<< "Client<> requires a valid network traffic annotation and "
|
||||
"only permits a missing annotation in tests.";
|
||||
request.network_traffic_annotation_tag =
|
||||
net::MutableNetworkTrafficAnnotationTag(MISSING_TRAFFIC_ANNOTATION);
|
||||
}
|
||||
|
||||
auto resource_request = std::make_unique<network::ResourceRequest>();
|
||||
resource_request->url = Endpoint::URL();
|
||||
resource_request->method = Request::Method();
|
||||
resource_request->load_flags = net::LOAD_BYPASS_CACHE |
|
||||
net::LOAD_DISABLE_CACHE |
|
||||
net::LOAD_DO_NOT_SAVE_COOKIES;
|
||||
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
|
||||
if constexpr (base::is_instantiation<Request, WithHeaders>) {
|
||||
resource_request->headers = std::move(request.headers);
|
||||
}
|
||||
|
||||
auto simple_url_loader = network::SimpleURLLoader::Create(
|
||||
std::move(resource_request),
|
||||
static_cast<net::NetworkTrafficAnnotationTag>(
|
||||
request.network_traffic_annotation_tag));
|
||||
simple_url_loader->SetAllowHttpErrorResults(true);
|
||||
if (auto upload_data = request.Serialize()) {
|
||||
CHECK(!upload_data->empty()) << "Failed to serialize request!";
|
||||
simple_url_loader->AttachStringForUpload(std::move(*upload_data),
|
||||
Request::ContentType());
|
||||
}
|
||||
simple_url_loader->SetTimeoutDuration(request.timeout_duration);
|
||||
simple_url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
|
||||
url_loader_factory.get(),
|
||||
base::BindOnce(OnResponse<Response>, std::move(callback),
|
||||
MaybeMoveLoader<C>(simple_url_loader)));
|
||||
|
||||
return MaybeMakeHandle<C>(simple_url_loader);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Response>
|
||||
static void OnResponse(
|
||||
base::OnceCallback<void(Response)> callback,
|
||||
std::variant<std::unique_ptr<network::SimpleURLLoader>,
|
||||
network::SimpleURLLoader*> simple_url_loader_ptr,
|
||||
std::optional<std::string> response_body) {
|
||||
const network::SimpleURLLoader& simple_url_loader = CHECK_DEREF(
|
||||
std::visit([](const auto& ptr) { return base::to_address(ptr); },
|
||||
simple_url_loader_ptr));
|
||||
|
||||
auto* response_info = simple_url_loader.ResponseInfo();
|
||||
auto headers = response_info ? response_info->headers : nullptr;
|
||||
|
||||
Response response(Response::Deserialize(simple_url_loader.NetError(),
|
||||
headers, std::move(response_body)));
|
||||
if constexpr (base::is_instantiation<Response, WithHeaders>) {
|
||||
response.headers = std::move(headers);
|
||||
}
|
||||
|
||||
std::move(callback).Run(std::move(response));
|
||||
}
|
||||
|
||||
// Depending on |C|, either takes ownership of (moves out) |simple_url_loader|
|
||||
// (non-cancelable case), or returns a raw pointer to the loader (cancelable
|
||||
// case).
|
||||
@@ -81,138 +156,6 @@ class Client {
|
||||
base::SequencedTaskRunner::GetCurrentDefault()));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// [[nodiscard]] enforces that callers retain the returned handle in
|
||||
// the cancelable case. It has no effect in the non-cancelable case,
|
||||
// where the return type is void.
|
||||
template <RequestCancelability C = RequestCancelability::kNonCancelable,
|
||||
typename Request,
|
||||
typename Response>
|
||||
requires(std::same_as<detail::MaybeStripWithHeaders<Request>,
|
||||
typename T::Request> &&
|
||||
std::same_as<detail::MaybeStripWithHeaders<Response>,
|
||||
typename T::Response>)
|
||||
[[nodiscard]] static auto Send(
|
||||
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
|
||||
Request request,
|
||||
base::OnceCallback<void(Response)> callback) {
|
||||
CHECK(url_loader_factory);
|
||||
if (!request.network_traffic_annotation_tag.is_valid()) {
|
||||
CHECK_IS_TEST()
|
||||
<< "Client<> requires a valid network traffic annotation and "
|
||||
"only permits a missing annotation in tests.";
|
||||
request.network_traffic_annotation_tag =
|
||||
net::MutableNetworkTrafficAnnotationTag(MISSING_TRAFFIC_ANNOTATION);
|
||||
}
|
||||
|
||||
auto resource_request = std::make_unique<network::ResourceRequest>();
|
||||
resource_request->url = T::URL();
|
||||
resource_request->method = Request::Method();
|
||||
resource_request->load_flags = net::LOAD_BYPASS_CACHE |
|
||||
net::LOAD_DISABLE_CACHE |
|
||||
net::LOAD_DO_NOT_SAVE_COOKIES;
|
||||
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
|
||||
if constexpr (base::is_instantiation<Request, WithHeaders>) {
|
||||
resource_request->headers = std::move(request.headers);
|
||||
}
|
||||
|
||||
auto simple_url_loader = network::SimpleURLLoader::Create(
|
||||
std::move(resource_request),
|
||||
static_cast<net::NetworkTrafficAnnotationTag>(
|
||||
request.network_traffic_annotation_tag));
|
||||
simple_url_loader->SetAllowHttpErrorResults(true);
|
||||
if (const auto dict = request.ToValue(); !dict.empty()) {
|
||||
auto json = base::WriteJson(dict).value_or("");
|
||||
CHECK(!json.empty()) << "Failed to serialize request to JSON!";
|
||||
simple_url_loader->AttachStringForUpload(std::move(json),
|
||||
"application/json");
|
||||
}
|
||||
|
||||
simple_url_loader->SetTimeoutDuration(request.timeout_duration);
|
||||
|
||||
simple_url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
|
||||
url_loader_factory.get(),
|
||||
base::BindOnce(OnResponse<Response>, std::move(callback),
|
||||
MaybeMoveLoader<C>(simple_url_loader)));
|
||||
|
||||
return MaybeMakeHandle<C>(simple_url_loader);
|
||||
}
|
||||
|
||||
private:
|
||||
// Body parsing behavior:
|
||||
//
|
||||
// response.body remains std::nullopt (no parsing attempted) when:
|
||||
// - network error occurred (response.net_error != net::OK)
|
||||
// - response info is missing (response_info is nullptr)
|
||||
// - response headers are missing (response_info->headers is nullptr)
|
||||
//
|
||||
// When parsing is attempted, two cases apply:
|
||||
//
|
||||
// 1. No body expected (std::is_empty_v<SuccessBody/ErrorBody>):
|
||||
// Whatever the server returns is ignored. We replace it with "{}" so
|
||||
// response.body is always a valid instance of the empty type, never
|
||||
// std::nullopt. This also means parsing failures are ignored, which
|
||||
// makes sense: if you don't expect data from the server, you shouldn't
|
||||
// care whether what it sent was parseable or not.
|
||||
//
|
||||
// 2. Body expected (!std::is_empty_v<SuccessBody/ErrorBody>):
|
||||
// response.body is std::nullopt (parsing failure) when response_body is:
|
||||
// - std::nullopt (no body received)
|
||||
// - empty string
|
||||
// - plain text (not JSON)
|
||||
// - invalid JSON
|
||||
// - valid JSON with wrong structure
|
||||
// In all these cases, either JSONReader::Read() or FromValue() fails.
|
||||
//
|
||||
// response.body is non-std::nullopt only when response_body contains
|
||||
// valid JSON matching the expected SuccessBody/ErrorBody structure.
|
||||
template <typename Response>
|
||||
static void OnResponse(
|
||||
base::OnceCallback<void(Response)> callback,
|
||||
std::variant<std::unique_ptr<network::SimpleURLLoader>,
|
||||
network::SimpleURLLoader*> simple_url_loader,
|
||||
std::optional<std::string> response_body) {
|
||||
const auto& simple_url_loader_ref = std::visit(
|
||||
[](const auto& ptr) -> network::SimpleURLLoader& {
|
||||
return CHECK_DEREF(ptr);
|
||||
},
|
||||
simple_url_loader);
|
||||
|
||||
Response response;
|
||||
response.net_error = simple_url_loader_ref.NetError();
|
||||
|
||||
auto* const response_info = simple_url_loader_ref.ResponseInfo();
|
||||
auto headers = response_info ? response_info->headers : nullptr;
|
||||
if (response.net_error != net::OK || !headers) {
|
||||
return std::move(callback).Run(std::move(response));
|
||||
}
|
||||
|
||||
response.status_code = headers->response_code();
|
||||
if constexpr (base::is_instantiation<Response, WithHeaders>) {
|
||||
response.headers = std::move(headers);
|
||||
}
|
||||
|
||||
const bool is_2xx = network::IsSuccessfulStatus(*response.status_code);
|
||||
if (is_2xx ? std::is_empty_v<typename Response::SuccessBody>
|
||||
: std::is_empty_v<typename Response::ErrorBody>) {
|
||||
response_body = "{}";
|
||||
}
|
||||
|
||||
const auto value =
|
||||
base::JSONReader::Read(response_body.value_or(""), base::JSON_PARSE_RFC)
|
||||
.value_or(base::Value());
|
||||
if (is_2xx) {
|
||||
response.body = Response::SuccessBody::FromValue(value);
|
||||
} else {
|
||||
response.body =
|
||||
Response::ErrorBody::FromValue(value).transform([](auto error_body) {
|
||||
return base::unexpected(std::move(error_body));
|
||||
});
|
||||
}
|
||||
|
||||
std::move(callback).Run(std::move(response));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace brave_account::endpoint_client
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/client.h"
|
||||
|
||||
#include <concepts>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -14,37 +15,36 @@
|
||||
#include "base/check.h"
|
||||
#include "base/check_deref.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/json/json_reader.h"
|
||||
#include "base/location.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/strings/to_string.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "base/test/bind.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/test/run_until.h"
|
||||
#include "base/test/task_environment.h"
|
||||
#include "base/test/test_future.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "base/types/expected.h"
|
||||
#include "base/types/is_instantiation.h"
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_account/endpoint_client/is_endpoint.h"
|
||||
#include "brave/components/brave_account/endpoint_client/is_request.h"
|
||||
#include "brave/components/brave_account/endpoint_client/is_response.h"
|
||||
#include "brave/components/brave_account/endpoint_client/is_response_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_empty_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_test_endpoint_bodies.h"
|
||||
#include "brave/components/brave_account/endpoint_client/maybe_strip_with_headers.h"
|
||||
#include "brave/components/brave_account/endpoint_client/protobuf_empty_body.pb.h"
|
||||
#include "brave/components/brave_account/endpoint_client/protobuf_test_endpoint_bodies.pb.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_handle.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "brave/components/brave_account/endpoint_client/static_string.h"
|
||||
#include "brave/components/brave_account/endpoint_client/with_headers.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/http/http_request_headers.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "net/http/http_status_code.h"
|
||||
#include "net/http/http_version.h"
|
||||
#include "services/network/public/cpp/data_element.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "services/network/public/cpp/resource_request_body.h"
|
||||
#include "services/network/public/cpp/simple_url_loader.h"
|
||||
#include "services/network/public/cpp/url_loader_completion_status.h"
|
||||
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
|
||||
#include "services/network/public/mojom/url_request.mojom-shared.h"
|
||||
#include "services/network/public/mojom/url_response_head.mojom.h"
|
||||
#include "services/network/test/test_url_loader_factory.h"
|
||||
#include "services/network/test/test_utils.h"
|
||||
@@ -75,310 +75,526 @@ bool operator==(const WithHeaders<Response<T, E>>& lhs,
|
||||
return lhs.headers->StrictlyEquals(*rhs.headers);
|
||||
}
|
||||
|
||||
bool operator==(const JSONRequestBody& lhs, const JSONRequestBody& rhs) {
|
||||
return lhs.request == rhs.request;
|
||||
}
|
||||
|
||||
bool operator==(const JSONSuccessBody& lhs, const JSONSuccessBody& rhs) {
|
||||
return lhs.success == rhs.success;
|
||||
}
|
||||
|
||||
bool operator==(const JSONErrorBody& lhs, const JSONErrorBody& rhs) {
|
||||
return lhs.error == rhs.error;
|
||||
}
|
||||
|
||||
bool operator==(const JSONEmptyBody&, const JSONEmptyBody&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator==(const ProtobufRequestBody& lhs,
|
||||
const ProtobufRequestBody& rhs) {
|
||||
return lhs.request() == rhs.request();
|
||||
}
|
||||
|
||||
bool operator==(const ProtobufSuccessBody& lhs,
|
||||
const ProtobufSuccessBody& rhs) {
|
||||
return lhs.success() == rhs.success();
|
||||
}
|
||||
|
||||
bool operator==(const ProtobufErrorBody& lhs, const ProtobufErrorBody& rhs) {
|
||||
return lhs.error() == rhs.error();
|
||||
}
|
||||
|
||||
bool operator==(const ProtobufEmptyBody&, const ProtobufEmptyBody&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <detail::StaticString Key>
|
||||
struct Body {
|
||||
base::DictValue ToValue() const {
|
||||
return base::DictValue().Set(Key.value, text);
|
||||
}
|
||||
|
||||
static std::optional<Body> FromValue(const base::Value& value) {
|
||||
const auto* dict = value.GetIfDict();
|
||||
if (!dict) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto* found = dict->FindString(Key.value);
|
||||
if (!found) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Body(*found);
|
||||
}
|
||||
|
||||
bool operator==(const Body& other) const { return text == other.text; }
|
||||
|
||||
std::string text;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Body<""> {
|
||||
base::DictValue ToValue() const { return base::DictValue(); }
|
||||
|
||||
static std::optional<Body> FromValue(const base::Value& value) {
|
||||
return value.is_dict() ? Body() : std::optional<Body>();
|
||||
}
|
||||
|
||||
bool operator==(const Body&) const { return true; }
|
||||
};
|
||||
|
||||
inline constexpr char kRequestKey[] = "request";
|
||||
inline constexpr char kSuccessKey[] = "success";
|
||||
inline constexpr char kErrorKey[] = "error";
|
||||
inline constexpr char kEmptyKey[] = "";
|
||||
|
||||
using TestRequestBody = Body<kRequestKey>;
|
||||
using TestSuccessBody = Body<kSuccessKey>;
|
||||
using TestErrorBody = Body<kErrorKey>;
|
||||
using TestEmptyBody = Body<kEmptyKey>;
|
||||
|
||||
template <typename ResponseT>
|
||||
struct Endpoint {
|
||||
using Request = POST<TestRequestBody>;
|
||||
template <detail::IsRequest RequestT, detail::IsResponse ResponseT>
|
||||
struct TestEndpoint {
|
||||
using Request = RequestT;
|
||||
using Response = ResponseT;
|
||||
static GURL URL() { return GURL("https://example.com/api/query"); }
|
||||
};
|
||||
|
||||
// Request: POST https://example.com/api/query { "request": "..." }
|
||||
// Success: { "success": "..." }
|
||||
// Error: { "error": "..." }
|
||||
using TestEndpoint = Endpoint<Response<TestSuccessBody, TestErrorBody>>;
|
||||
// Request: POST https://example.com/api/query { "request": "abc" }
|
||||
// Success: { "success": "ok" }
|
||||
// Error: { "error": "nope" }
|
||||
using JSONEndpointSuccessError =
|
||||
TestEndpoint<POST<JSONRequestBody>,
|
||||
Response<JSONSuccessBody, JSONErrorBody>>;
|
||||
|
||||
// Request: POST https://example.com/api/query { "request": "..." }
|
||||
// Success: ignored
|
||||
// Error: { "error": "..." }
|
||||
using TestEndpointWithEmptySuccessBody =
|
||||
Endpoint<Response<TestEmptyBody, TestErrorBody>>;
|
||||
// Request: POST https://example.com/api/query { "request": "abc" }
|
||||
// Success: ignored (response body not parsed)
|
||||
// Error: { "error": "nope" }
|
||||
using JSONEndpointEmptyError =
|
||||
TestEndpoint<POST<JSONRequestBody>, Response<JSONEmptyBody, JSONErrorBody>>;
|
||||
|
||||
// Request: POST https://example.com/api/query { "request": "..." }
|
||||
// Success: { "success": "..." }
|
||||
// Error: ignored
|
||||
using TestEndpointWithEmptyErrorBody =
|
||||
Endpoint<Response<TestSuccessBody, TestEmptyBody>>;
|
||||
// Request: POST https://example.com/api/query { "request": "abc" }
|
||||
// Success: { "success": "ok" }
|
||||
// Error: ignored (response body not parsed)
|
||||
using JSONEndpointSuccessEmpty =
|
||||
TestEndpoint<POST<JSONRequestBody>,
|
||||
Response<JSONSuccessBody, JSONEmptyBody>>;
|
||||
|
||||
template <typename Request, typename Response>
|
||||
// Request: POST https://example.com/api/query \x0A\x03abc
|
||||
// Success: \x0A\x02ok
|
||||
// Error: \x0A\x04nope
|
||||
using ProtobufEndpointSuccessError =
|
||||
TestEndpoint<POST<ProtobufRequestBody>,
|
||||
Response<ProtobufSuccessBody, ProtobufErrorBody>>;
|
||||
|
||||
// Request: POST https://example.com/api/query \x0A\x03abc
|
||||
// Success: ignored (response body not parsed)
|
||||
// Error: \x0A\x04nope
|
||||
using ProtobufEndpointEmptyError =
|
||||
TestEndpoint<POST<ProtobufRequestBody>,
|
||||
Response<ProtobufEmptyBody, ProtobufErrorBody>>;
|
||||
|
||||
// Request: POST https://example.com/api/query \x0A\x03abc
|
||||
// Success: \x0A\x02ok
|
||||
// Error: ignored (response body not parsed)
|
||||
using ProtobufEndpointSuccessEmpty =
|
||||
TestEndpoint<POST<ProtobufRequestBody>,
|
||||
Response<ProtobufSuccessBody, ProtobufEmptyBody>>;
|
||||
|
||||
template <typename Response>
|
||||
requires detail::IsResponse<detail::MaybeStripWithHeaders<Response>>
|
||||
struct TestCase {
|
||||
std::string test_name;
|
||||
Request request;
|
||||
int net_error;
|
||||
std::optional<net::HttpStatusCode> status_code;
|
||||
std::string body;
|
||||
Response expected_response;
|
||||
Response response;
|
||||
};
|
||||
|
||||
template <typename Endpoint,
|
||||
typename Request = typename Endpoint::Request,
|
||||
typename Response = typename Endpoint::Response>
|
||||
class ClientTest : public testing::TestWithParam<TestCase<Request, Response>> {
|
||||
template <IsEndpoint Endpoint, typename Request, typename Response>
|
||||
requires(std::same_as<detail::MaybeStripWithHeaders<Request>,
|
||||
typename Endpoint::Request> &&
|
||||
std::same_as<detail::MaybeStripWithHeaders<Response>,
|
||||
typename Endpoint::Response>)
|
||||
class ClientTest : public testing::TestWithParam<const TestCase<Response>*> {
|
||||
public:
|
||||
using TestCase = typename ClientTest::ParamType;
|
||||
|
||||
static constexpr auto kNameGenerator = [](const auto& info) {
|
||||
return info.param.test_name;
|
||||
return CHECK_DEREF(info.param).test_name;
|
||||
};
|
||||
|
||||
static std::vector<TestCase> GenerateTestCases() {
|
||||
auto make_request = [] {
|
||||
Request request;
|
||||
request.text = "request";
|
||||
|
||||
if constexpr (base::is_instantiation<Request, WithHeaders>) {
|
||||
SetBearerToken(request, "12345");
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
auto make_response = [](int net_error,
|
||||
std::optional<net::HttpStatusCode> status_code,
|
||||
auto body) {
|
||||
Response response;
|
||||
response.net_error = net_error;
|
||||
response.status_code = status_code;
|
||||
response.body = std::move(body);
|
||||
|
||||
if constexpr (base::is_instantiation<Response, WithHeaders>) {
|
||||
if (status_code) {
|
||||
response.headers =
|
||||
net::HttpResponseHeaders::Builder(
|
||||
{1, 1},
|
||||
absl::StrFormat("%d %s", *status_code,
|
||||
net::GetHttpReasonPhrase(*status_code)))
|
||||
.AddHeader("X-Test-Header", "test-value")
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
auto make_success_body = [](bool non_empty_nullopt) {
|
||||
using SuccessBody = typename Endpoint::Response::SuccessBody;
|
||||
|
||||
if constexpr (std::is_empty_v<SuccessBody>) {
|
||||
return SuccessBody();
|
||||
} else {
|
||||
return non_empty_nullopt ? std::optional<SuccessBody>()
|
||||
: SuccessBody("success");
|
||||
}
|
||||
};
|
||||
|
||||
auto make_error_body = [](bool non_empty_nullopt) {
|
||||
using ErrorBody = typename Endpoint::Response::ErrorBody;
|
||||
|
||||
if constexpr (std::is_empty_v<ErrorBody>) {
|
||||
return base::unexpected(ErrorBody());
|
||||
} else {
|
||||
return non_empty_nullopt ? std::optional<base::unexpected<ErrorBody>>()
|
||||
: base::unexpected(ErrorBody("error"));
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
TestCase{.test_name = "net_error",
|
||||
.request = make_request(),
|
||||
.net_error = net::ERR_FAILED,
|
||||
.status_code = std::nullopt,
|
||||
.body = "",
|
||||
.expected_response = make_response(
|
||||
net::ERR_FAILED, std::nullopt, std::nullopt)},
|
||||
TestCase{.test_name = "no_response_headers",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = std::nullopt,
|
||||
.body = "",
|
||||
.expected_response =
|
||||
make_response(net::OK, std::nullopt, std::nullopt)},
|
||||
TestCase{.test_name = "success_empty_response",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = "",
|
||||
.expected_response = make_response(net::OK, net::HTTP_OK,
|
||||
make_success_body(true))},
|
||||
TestCase{.test_name = "success_plain_text_response",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = "plain text",
|
||||
.expected_response = make_response(net::OK, net::HTTP_OK,
|
||||
make_success_body(true))},
|
||||
TestCase{.test_name = "success_invalid_json_response",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = R"({"invalid": json})",
|
||||
.expected_response = make_response(net::OK, net::HTTP_OK,
|
||||
make_success_body(true))},
|
||||
TestCase{.test_name = "success_valid_json_lacks_success_field",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = R"({"valid": "json"})",
|
||||
.expected_response = make_response(net::OK, net::HTTP_OK,
|
||||
make_success_body(true))},
|
||||
TestCase{.test_name = "success_valid_json_has_success_field",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = R"({"success": "success"})",
|
||||
.expected_response = make_response(net::OK, net::HTTP_OK,
|
||||
make_success_body(false))},
|
||||
TestCase{.test_name = "error_empty_response",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body = "",
|
||||
.expected_response = make_response(
|
||||
net::OK, net::HTTP_BAD_REQUEST, make_error_body(true))},
|
||||
TestCase{.test_name = "error_plain_text_response",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body = "plain text",
|
||||
.expected_response = make_response(
|
||||
net::OK, net::HTTP_BAD_REQUEST, make_error_body(true))},
|
||||
TestCase{.test_name = "error_invalid_json_response",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body = R"({"invalid": json})",
|
||||
.expected_response = make_response(
|
||||
net::OK, net::HTTP_BAD_REQUEST, make_error_body(true))},
|
||||
TestCase{.test_name = "error_valid_json_lacks_error_field",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body = R"({"valid": "json"})",
|
||||
.expected_response = make_response(
|
||||
net::OK, net::HTTP_BAD_REQUEST, make_error_body(true))},
|
||||
TestCase{.test_name = "error_valid_json_has_error_field",
|
||||
.request = make_request(),
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body = R"({"error": "error"})",
|
||||
.expected_response = make_response(
|
||||
net::OK, net::HTTP_BAD_REQUEST, make_error_body(false))}};
|
||||
static auto GenerateTestCases() {
|
||||
return testing::Values(NetError(), NoResponseHeaders(),
|
||||
SuccessEmptyResponse(), SuccessMalformedResponse(),
|
||||
SuccessUnexpectedResponse(), SuccessValidResponse(),
|
||||
ErrorEmptyResponse(), ErrorMalformedResponse(),
|
||||
ErrorUnexpectedResponse(), ErrorValidResponse());
|
||||
}
|
||||
|
||||
protected:
|
||||
void Interceptor(const network::ResourceRequest& resource_request) {
|
||||
const auto& test_case = this->GetParam();
|
||||
|
||||
EXPECT_EQ(resource_request.method, Request::Method());
|
||||
EXPECT_EQ(resource_request.url, Endpoint::URL());
|
||||
const auto& request_body = CHECK_DEREF(resource_request.request_body);
|
||||
const auto& elements = CHECK_DEREF(request_body.elements());
|
||||
CHECK_EQ(elements.size(), 1u);
|
||||
const auto& element = elements.front();
|
||||
EXPECT_EQ(element.type(), network::DataElement::Tag::kBytes);
|
||||
const auto body = base::JSONReader::Read(
|
||||
element.As<network::DataElementBytes>().AsStringPiece(),
|
||||
base::JSON_PARSE_RFC);
|
||||
CHECK(body && body->is_dict());
|
||||
const auto* request = body->GetDict().FindString(kRequestKey);
|
||||
EXPECT_NE(request, nullptr);
|
||||
EXPECT_EQ(*request, test_case.request.text);
|
||||
EXPECT_EQ(resource_request.headers.GetHeader(
|
||||
net::HttpRequestHeaders::kContentType),
|
||||
"application/json");
|
||||
if constexpr (base::is_instantiation<Request, WithHeaders>) {
|
||||
EXPECT_EQ(resource_request.headers.GetHeader(
|
||||
net::HttpRequestHeaders::kAuthorization),
|
||||
"Bearer 12345");
|
||||
}
|
||||
|
||||
auto head = test_case.status_code
|
||||
? network::CreateURLResponseHead(*test_case.status_code)
|
||||
: network::mojom::URLResponseHead::New();
|
||||
if constexpr (base::is_instantiation<Response, WithHeaders>) {
|
||||
head->headers = test_case.expected_response.headers;
|
||||
}
|
||||
test_url_loader_factory_.AddResponse(
|
||||
resource_request.url, std::move(head), test_case.body,
|
||||
network::URLLoaderCompletionStatus(test_case.net_error));
|
||||
}
|
||||
|
||||
void RunTestCase() {
|
||||
const auto& test_case = this->GetParam();
|
||||
const auto& test_case = CHECK_DEREF(this->GetParam());
|
||||
|
||||
test_url_loader_factory_.SetInterceptor(
|
||||
base::BindRepeating(&ClientTest::Interceptor, base::Unretained(this)));
|
||||
|
||||
base::test::TestFuture<Response> future;
|
||||
Client<Endpoint>::Send(test_url_loader_factory_.GetSafeWeakWrapper(),
|
||||
test_case.request, future.GetCallback());
|
||||
EXPECT_EQ(future.Take(), test_case.expected_response);
|
||||
MakeRequest(), future.GetCallback());
|
||||
EXPECT_EQ(future.Take(), test_case.response);
|
||||
}
|
||||
|
||||
private:
|
||||
using TestCase = TestCase<Response>;
|
||||
|
||||
enum class BodyCase { kMalformed, kUnexpected, kValid };
|
||||
|
||||
static const TestCase* NetError() {
|
||||
static const base::NoDestructor<TestCase> kNetError({
|
||||
.test_name = "net_error",
|
||||
.net_error = net::ERR_FAILED,
|
||||
.status_code = std::nullopt,
|
||||
.body = "",
|
||||
.response = MakeResponse(net::ERR_FAILED, std::nullopt, std::nullopt),
|
||||
});
|
||||
return kNetError.get();
|
||||
}
|
||||
|
||||
static const TestCase* NoResponseHeaders() {
|
||||
static const base::NoDestructor<TestCase> kNoResponseHeaders({
|
||||
.test_name = "no_response_headers",
|
||||
.net_error = net::OK,
|
||||
.status_code = std::nullopt,
|
||||
.body = "",
|
||||
.response = MakeResponse(net::OK, std::nullopt, std::nullopt),
|
||||
});
|
||||
return kNoResponseHeaders.get();
|
||||
}
|
||||
|
||||
static const TestCase* SuccessEmptyResponse() {
|
||||
static const base::NoDestructor<TestCase> kSuccessEmptyResponse({
|
||||
.test_name = "success_empty_response",
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = "",
|
||||
.response = MakeResponse(net::OK, net::HTTP_OK, std::nullopt),
|
||||
});
|
||||
return kSuccessEmptyResponse.get();
|
||||
}
|
||||
|
||||
static const TestCase* SuccessMalformedResponse() {
|
||||
static const base::NoDestructor<TestCase> kSuccessMalformedResponse({
|
||||
.test_name = "success_malformed_response",
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = MakeBody<typename Endpoint::Response::SuccessBody>(
|
||||
BodyCase::kMalformed),
|
||||
.response = MakeResponse(net::OK, net::HTTP_OK, BodyCase::kMalformed),
|
||||
});
|
||||
return kSuccessMalformedResponse.get();
|
||||
}
|
||||
|
||||
static const TestCase* SuccessUnexpectedResponse() {
|
||||
static const base::NoDestructor<TestCase> kSuccessUnexpectedResponse({
|
||||
.test_name = "success_unexpected_response",
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = MakeBody<typename Endpoint::Response::SuccessBody>(
|
||||
BodyCase::kUnexpected),
|
||||
.response = MakeResponse(net::OK, net::HTTP_OK, BodyCase::kUnexpected),
|
||||
});
|
||||
return kSuccessUnexpectedResponse.get();
|
||||
}
|
||||
|
||||
static const TestCase* SuccessValidResponse() {
|
||||
static const base::NoDestructor<TestCase> kSuccessValidResponse({
|
||||
.test_name = "success_valid_response",
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_OK,
|
||||
.body = MakeBody<typename Endpoint::Response::SuccessBody>(
|
||||
BodyCase::kValid),
|
||||
.response = MakeResponse(net::OK, net::HTTP_OK, BodyCase::kValid),
|
||||
});
|
||||
return kSuccessValidResponse.get();
|
||||
}
|
||||
|
||||
static const TestCase* ErrorEmptyResponse() {
|
||||
static const base::NoDestructor<TestCase> kErrorEmptyResponse({
|
||||
.test_name = "error_empty_response",
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body = "",
|
||||
.response = MakeResponse(net::OK, net::HTTP_BAD_REQUEST, std::nullopt),
|
||||
});
|
||||
return kErrorEmptyResponse.get();
|
||||
}
|
||||
|
||||
static const TestCase* ErrorMalformedResponse() {
|
||||
static const base::NoDestructor<TestCase> kErrorMalformedResponse({
|
||||
.test_name = "error_malformed_response",
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body = MakeBody<typename Endpoint::Response::ErrorBody>(
|
||||
BodyCase::kMalformed),
|
||||
.response =
|
||||
MakeResponse(net::OK, net::HTTP_BAD_REQUEST, BodyCase::kMalformed),
|
||||
});
|
||||
return kErrorMalformedResponse.get();
|
||||
}
|
||||
|
||||
static const TestCase* ErrorUnexpectedResponse() {
|
||||
static const base::NoDestructor<TestCase> kErrorUnexpectedResponse({
|
||||
.test_name = "error_unexpected_response",
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body = MakeBody<typename Endpoint::Response::ErrorBody>(
|
||||
BodyCase::kUnexpected),
|
||||
.response =
|
||||
MakeResponse(net::OK, net::HTTP_BAD_REQUEST, BodyCase::kUnexpected),
|
||||
});
|
||||
return kErrorUnexpectedResponse.get();
|
||||
}
|
||||
|
||||
static const TestCase* ErrorValidResponse() {
|
||||
static const base::NoDestructor<TestCase> kErrorValidResponse({
|
||||
.test_name = "error_valid_response",
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_BAD_REQUEST,
|
||||
.body =
|
||||
MakeBody<typename Endpoint::Response::ErrorBody>(BodyCase::kValid),
|
||||
.response =
|
||||
MakeResponse(net::OK, net::HTTP_BAD_REQUEST, BodyCase::kValid),
|
||||
});
|
||||
return kErrorValidResponse.get();
|
||||
}
|
||||
|
||||
template <std::same_as<JSONRequestBody> RequestBody>
|
||||
static RequestBody MakeRequestBody() {
|
||||
RequestBody request_body;
|
||||
request_body.request = "request";
|
||||
return request_body;
|
||||
}
|
||||
|
||||
template <std::same_as<ProtobufRequestBody> RequestBody>
|
||||
static RequestBody MakeRequestBody() {
|
||||
RequestBody request_body;
|
||||
request_body.set_request("request");
|
||||
return request_body;
|
||||
}
|
||||
|
||||
static Request MakeRequest() {
|
||||
Request request;
|
||||
request.body = MakeRequestBody<typename Endpoint::Request::Body>();
|
||||
|
||||
if constexpr (base::is_instantiation<Request, WithHeaders>) {
|
||||
SetBearerToken(request, "12345");
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
template <detail::IsJSONResponseBody ResponseBody>
|
||||
static auto MakeResponseBody(std::optional<BodyCase> body_case) {
|
||||
std::optional<ResponseBody> response_body;
|
||||
|
||||
if constexpr (std::same_as<ResponseBody, JSONEmptyBody>) {
|
||||
response_body.emplace();
|
||||
} else if (body_case) {
|
||||
switch (*body_case) {
|
||||
case BodyCase::kMalformed:
|
||||
break;
|
||||
case BodyCase::kUnexpected:
|
||||
break;
|
||||
case BodyCase::kValid:
|
||||
if constexpr (std::same_as<ResponseBody, JSONSuccessBody>) {
|
||||
response_body.emplace().success = "success";
|
||||
} else {
|
||||
static_assert(std::same_as<ResponseBody, JSONErrorBody>);
|
||||
response_body.emplace().error = "error";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response_body;
|
||||
}
|
||||
|
||||
template <detail::IsProtobufResponseBody ResponseBody>
|
||||
static auto MakeResponseBody(std::optional<BodyCase> body_case) {
|
||||
std::optional<ResponseBody> response_body;
|
||||
|
||||
if constexpr (std::same_as<ResponseBody, ProtobufEmptyBody>) {
|
||||
response_body.emplace();
|
||||
} else if (body_case) {
|
||||
switch (*body_case) {
|
||||
case BodyCase::kMalformed:
|
||||
break;
|
||||
case BodyCase::kUnexpected:
|
||||
// Unexpected payloads are well-formed protobuf wire formats that do
|
||||
// not match the expected ResponseBody schema (e.g. contain only
|
||||
// unknown fields). Parsing succeeds, but all fields are ignored,
|
||||
// yielding a default-constructed ResponseBody. See
|
||||
// Response<>::Deserialize<>() for details on protobuf parsing
|
||||
// semantics.
|
||||
response_body.emplace();
|
||||
break;
|
||||
case BodyCase::kValid:
|
||||
if constexpr (std::same_as<ResponseBody, ProtobufSuccessBody>) {
|
||||
response_body.emplace().set_success("success");
|
||||
} else {
|
||||
static_assert(std::same_as<ResponseBody, ProtobufErrorBody>);
|
||||
response_body.emplace().set_error("error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response_body;
|
||||
}
|
||||
|
||||
static Response MakeResponse(int net_error,
|
||||
std::optional<net::HttpStatusCode> status_code,
|
||||
std::optional<BodyCase> body_case) {
|
||||
Response response;
|
||||
response.net_error = net_error;
|
||||
response.status_code = status_code;
|
||||
|
||||
if (net_error == net::OK && status_code) {
|
||||
if (network::IsSuccessfulStatus(*status_code)) { // 2xx
|
||||
response.body =
|
||||
MakeResponseBody<typename Endpoint::Response::SuccessBody>(
|
||||
body_case);
|
||||
} else { // non-2xx
|
||||
response.body =
|
||||
MakeResponseBody<typename Endpoint::Response::ErrorBody>(body_case)
|
||||
.transform([](auto error_body) {
|
||||
return base::unexpected(std::move(error_body));
|
||||
});
|
||||
}
|
||||
|
||||
if constexpr (base::is_instantiation<Response, WithHeaders>) {
|
||||
response.headers =
|
||||
net::HttpResponseHeaders::Builder(
|
||||
{1, 1}, absl::StrFormat("%d %s", *status_code,
|
||||
net::GetHttpReasonPhrase(*status_code)))
|
||||
.AddHeader("X-Test-Header", "test-value")
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
template <detail::IsJSONResponseBody ResponseBody>
|
||||
static std::string MakeBody(BodyCase body_case) {
|
||||
switch (body_case) {
|
||||
case BodyCase::kMalformed:
|
||||
return R"({"invalid": json})";
|
||||
case BodyCase::kUnexpected:
|
||||
return R"({"valid": "json"})";
|
||||
case BodyCase::kValid: {
|
||||
ResponseBody response_body;
|
||||
|
||||
if constexpr (!std::same_as<ResponseBody, JSONEmptyBody>) {
|
||||
if constexpr (std::same_as<ResponseBody, JSONSuccessBody>) {
|
||||
response_body.success = "success";
|
||||
} else {
|
||||
static_assert(std::same_as<ResponseBody, JSONErrorBody>);
|
||||
response_body.error = "error";
|
||||
}
|
||||
}
|
||||
|
||||
return CHECK_DEREF(base::WriteJson(response_body.ToValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <detail::IsProtobufResponseBody ResponseBody>
|
||||
static std::string MakeBody(BodyCase body_case) {
|
||||
switch (body_case) {
|
||||
case BodyCase::kMalformed:
|
||||
// Field 1, length=10, but only 8 bytes follow ("tooshort").
|
||||
return "\x0A\x0Atooshort";
|
||||
case BodyCase::kUnexpected:
|
||||
// Field 2, length=10, value="unexpected".
|
||||
// Well-formed protobuf payload containing only unknown fields.
|
||||
// ParseFromString() succeeds, yielding a default-constructed
|
||||
// ResponseBody. See Response<>::Deserialize<>() for details on protobuf
|
||||
// parsing semantics.
|
||||
return "\x12\x0Aunexpected";
|
||||
case BodyCase::kValid: {
|
||||
ResponseBody response_body;
|
||||
|
||||
if constexpr (!std::same_as<ResponseBody, ProtobufEmptyBody>) {
|
||||
if constexpr (std::same_as<ResponseBody, ProtobufSuccessBody>) {
|
||||
response_body.set_success("success");
|
||||
} else {
|
||||
static_assert(std::same_as<ResponseBody, ProtobufErrorBody>);
|
||||
response_body.set_error("error");
|
||||
}
|
||||
}
|
||||
|
||||
return response_body.SerializeAsString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <std::same_as<JSONRequestBody> RequestBody>
|
||||
RequestBody RequestBodyFrom(std::string_view bytes) {
|
||||
return CHECK_DEREF(RequestBody::FromValue(
|
||||
CHECK_DEREF(base::JSONReader::Read(bytes, base::JSON_PARSE_RFC))));
|
||||
}
|
||||
|
||||
template <std::same_as<ProtobufRequestBody> RequestBody>
|
||||
RequestBody RequestBodyFrom(std::string_view bytes) {
|
||||
RequestBody body;
|
||||
CHECK(body.ParseFromString(bytes));
|
||||
return body;
|
||||
}
|
||||
|
||||
void Interceptor(const network::ResourceRequest& resource_request) {
|
||||
EXPECT_EQ(resource_request.url, Endpoint::URL());
|
||||
EXPECT_EQ(resource_request.method, Request::Method());
|
||||
EXPECT_EQ(resource_request.headers.GetHeader(
|
||||
net::HttpRequestHeaders::kContentType),
|
||||
Request::ContentType());
|
||||
|
||||
const auto& request_body = CHECK_DEREF(resource_request.request_body);
|
||||
const auto& elements = CHECK_DEREF(request_body.elements());
|
||||
CHECK_EQ(elements.size(), 1u);
|
||||
const auto& element = elements.front();
|
||||
EXPECT_EQ(element.type(), network::DataElement::Tag::kBytes);
|
||||
const auto bytes = element.As<network::DataElementBytes>().AsStringPiece();
|
||||
EXPECT_EQ(RequestBodyFrom<typename Endpoint::Request::Body>(bytes),
|
||||
MakeRequest().body);
|
||||
|
||||
if constexpr (base::is_instantiation<Request, WithHeaders>) {
|
||||
EXPECT_EQ(resource_request.headers.GetHeader(
|
||||
net::HttpRequestHeaders::kAuthorization),
|
||||
"Bearer 12345");
|
||||
}
|
||||
|
||||
const auto& test_case = CHECK_DEREF(this->GetParam());
|
||||
auto head = test_case.status_code
|
||||
? network::CreateURLResponseHead(*test_case.status_code)
|
||||
: network::mojom::URLResponseHead::New();
|
||||
if constexpr (base::is_instantiation<Response, WithHeaders>) {
|
||||
head->headers = test_case.response.headers;
|
||||
}
|
||||
|
||||
test_url_loader_factory_.AddResponse(
|
||||
resource_request.url, std::move(head), test_case.body,
|
||||
network::URLLoaderCompletionStatus(test_case.net_error));
|
||||
}
|
||||
|
||||
base::test::TaskEnvironment task_environment_;
|
||||
network::TestURLLoaderFactory test_url_loader_factory_;
|
||||
};
|
||||
|
||||
using ClientTestPlainRequest = ClientTest<TestEndpoint>;
|
||||
using ClientTestJSONPlainRequest =
|
||||
ClientTest<JSONEndpointSuccessError,
|
||||
JSONEndpointSuccessError::Request,
|
||||
JSONEndpointSuccessError::Response>;
|
||||
|
||||
using ClientTestRequestWithHeaders =
|
||||
ClientTest<TestEndpoint, WithHeaders<TestEndpoint::Request>>;
|
||||
using ClientTestJSONRequestWithHeaders =
|
||||
ClientTest<JSONEndpointSuccessError,
|
||||
WithHeaders<JSONEndpointSuccessError::Request>,
|
||||
JSONEndpointSuccessError::Response>;
|
||||
|
||||
using ClientTestResponseWithHeaders =
|
||||
ClientTest<TestEndpoint,
|
||||
TestEndpoint::Request,
|
||||
WithHeaders<TestEndpoint::Response>>;
|
||||
using ClientTestJSONResponseWithHeaders =
|
||||
ClientTest<JSONEndpointSuccessError,
|
||||
JSONEndpointSuccessError::Request,
|
||||
WithHeaders<JSONEndpointSuccessError::Response>>;
|
||||
|
||||
using ClientTestEmptySuccessBody = ClientTest<TestEndpointWithEmptySuccessBody>;
|
||||
using ClientTestJSONEmptySuccessBody =
|
||||
ClientTest<JSONEndpointEmptyError,
|
||||
JSONEndpointEmptyError::Request,
|
||||
JSONEndpointEmptyError::Response>;
|
||||
|
||||
using ClientTestEmptyErrorBody = ClientTest<TestEndpointWithEmptyErrorBody>;
|
||||
using ClientTestJSONEmptyErrorBody =
|
||||
ClientTest<JSONEndpointSuccessEmpty,
|
||||
JSONEndpointSuccessEmpty::Request,
|
||||
JSONEndpointSuccessEmpty::Response>;
|
||||
|
||||
using ClientTestProtobufPlainRequest =
|
||||
ClientTest<ProtobufEndpointSuccessError,
|
||||
ProtobufEndpointSuccessError::Request,
|
||||
ProtobufEndpointSuccessError::Response>;
|
||||
|
||||
using ClientTestProtobufRequestWithHeaders =
|
||||
ClientTest<ProtobufEndpointSuccessError,
|
||||
WithHeaders<ProtobufEndpointSuccessError::Request>,
|
||||
ProtobufEndpointSuccessError::Response>;
|
||||
|
||||
using ClientTestProtobufResponseWithHeaders =
|
||||
ClientTest<ProtobufEndpointSuccessError,
|
||||
ProtobufEndpointSuccessError::Request,
|
||||
WithHeaders<ProtobufEndpointSuccessError::Response>>;
|
||||
|
||||
using ClientTestProtobufEmptySuccessBody =
|
||||
ClientTest<ProtobufEndpointEmptyError,
|
||||
ProtobufEndpointEmptyError::Request,
|
||||
ProtobufEndpointEmptyError::Response>;
|
||||
|
||||
using ClientTestProtobufEmptyErrorBody =
|
||||
ClientTest<ProtobufEndpointSuccessEmpty,
|
||||
ProtobufEndpointSuccessEmpty::Request,
|
||||
ProtobufEndpointSuccessEmpty::Response>;
|
||||
|
||||
enum class CancelRequestOn { kSameSequence, kDifferentSequence };
|
||||
|
||||
@@ -391,62 +607,105 @@ class ClientTestCancelableRequest
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_P(ClientTestPlainRequest, Send) {
|
||||
TEST_P(ClientTestJSONPlainRequest, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(JSONPlainRequest,
|
||||
ClientTestJSONPlainRequest,
|
||||
ClientTestJSONPlainRequest::GenerateTestCases(),
|
||||
ClientTestJSONPlainRequest::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestJSONRequestWithHeaders, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(JSONRequestWithHeaders,
|
||||
ClientTestJSONRequestWithHeaders,
|
||||
ClientTestJSONRequestWithHeaders::GenerateTestCases(),
|
||||
ClientTestJSONRequestWithHeaders::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestJSONResponseWithHeaders, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(JSONResponseWithHeaders,
|
||||
ClientTestJSONResponseWithHeaders,
|
||||
ClientTestJSONResponseWithHeaders::GenerateTestCases(),
|
||||
ClientTestJSONResponseWithHeaders::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestJSONEmptySuccessBody, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(JSONEmptySuccessBody,
|
||||
ClientTestJSONEmptySuccessBody,
|
||||
ClientTestJSONEmptySuccessBody::GenerateTestCases(),
|
||||
ClientTestJSONEmptySuccessBody::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestJSONEmptyErrorBody, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(JSONEmptyErrorBody,
|
||||
ClientTestJSONEmptyErrorBody,
|
||||
ClientTestJSONEmptyErrorBody::GenerateTestCases(),
|
||||
ClientTestJSONEmptyErrorBody::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestProtobufPlainRequest, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ProtobufPlainRequest,
|
||||
ClientTestProtobufPlainRequest,
|
||||
ClientTestProtobufPlainRequest::GenerateTestCases(),
|
||||
ClientTestProtobufPlainRequest::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestProtobufRequestWithHeaders, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
PlainRequest,
|
||||
ClientTestPlainRequest,
|
||||
testing::ValuesIn(ClientTestPlainRequest::GenerateTestCases()),
|
||||
ClientTestPlainRequest::kNameGenerator);
|
||||
ProtobufRequestWithHeaders,
|
||||
ClientTestProtobufRequestWithHeaders,
|
||||
ClientTestProtobufRequestWithHeaders::GenerateTestCases(),
|
||||
ClientTestProtobufRequestWithHeaders::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestRequestWithHeaders, Send) {
|
||||
TEST_P(ClientTestProtobufResponseWithHeaders, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
RequestWithHeaders,
|
||||
ClientTestRequestWithHeaders,
|
||||
testing::ValuesIn(ClientTestRequestWithHeaders::GenerateTestCases()),
|
||||
ClientTestRequestWithHeaders::kNameGenerator);
|
||||
ProtobufResponseWithHeaders,
|
||||
ClientTestProtobufResponseWithHeaders,
|
||||
ClientTestProtobufResponseWithHeaders::GenerateTestCases(),
|
||||
ClientTestProtobufResponseWithHeaders::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestResponseWithHeaders, Send) {
|
||||
TEST_P(ClientTestProtobufEmptySuccessBody, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
ResponseWithHeaders,
|
||||
ClientTestResponseWithHeaders,
|
||||
testing::ValuesIn(ClientTestResponseWithHeaders::GenerateTestCases()),
|
||||
ClientTestResponseWithHeaders::kNameGenerator);
|
||||
ProtobufEmptySuccessBody,
|
||||
ClientTestProtobufEmptySuccessBody,
|
||||
ClientTestProtobufEmptySuccessBody::GenerateTestCases(),
|
||||
ClientTestProtobufEmptySuccessBody::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestEmptySuccessBody, Send) {
|
||||
TEST_P(ClientTestProtobufEmptyErrorBody, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EmptySuccessBody,
|
||||
ClientTestEmptySuccessBody,
|
||||
testing::ValuesIn(ClientTestEmptySuccessBody::GenerateTestCases()),
|
||||
ClientTestEmptySuccessBody::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestEmptyErrorBody, Send) {
|
||||
RunTestCase();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EmptyErrorBody,
|
||||
ClientTestEmptyErrorBody,
|
||||
testing::ValuesIn(ClientTestEmptyErrorBody::GenerateTestCases()),
|
||||
ClientTestEmptyErrorBody::kNameGenerator);
|
||||
INSTANTIATE_TEST_SUITE_P(ProtobufEmptyErrorBody,
|
||||
ClientTestProtobufEmptyErrorBody,
|
||||
ClientTestProtobufEmptyErrorBody::GenerateTestCases(),
|
||||
ClientTestProtobufEmptyErrorBody::kNameGenerator);
|
||||
|
||||
TEST_P(ClientTestCancelableRequest, Cancel) {
|
||||
base::test::TestFuture<TestEndpoint::Response> future;
|
||||
base::test::TestFuture<JSONEndpointSuccessError::Response> future;
|
||||
RequestHandle request_handle =
|
||||
Client<TestEndpoint>::Send<RequestCancelability::kCancelable>(
|
||||
Client<JSONEndpointSuccessError>::Send<RequestCancelability::kCancelable>(
|
||||
test_url_loader_factory_.GetSafeWeakWrapper(),
|
||||
TestEndpoint::Request{{"cancel me"}}, future.GetCallback());
|
||||
JSONEndpointSuccessError::Request(), future.GetCallback());
|
||||
|
||||
auto weak_simple_url_loader =
|
||||
CHECK_DEREF(static_cast<network::SimpleURLLoader*>(request_handle.get()))
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINT_CLIENT_CONCEPT_TEST_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINT_CLIENT_CONCEPT_TEST_H_
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace brave_account::endpoint_client {
|
||||
|
||||
struct ConceptTest {
|
||||
template <typename T>
|
||||
struct Fixture : testing::Test {
|
||||
using TestType = std::tuple_element_t<0, T>;
|
||||
using ExpectedResult = std::tuple_element_t<1, T>;
|
||||
};
|
||||
|
||||
struct NameGenerator {
|
||||
template <typename T>
|
||||
static std::string GetName(int) {
|
||||
using TestType = std::tuple_element_t<0, T>;
|
||||
using ExpectedResult = std::tuple_element_t<1, T>;
|
||||
return std::string(TestType::kName) + "_does" +
|
||||
(ExpectedResult::value ? "" : "_not");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace brave_account::endpoint_client
|
||||
|
||||
#endif // BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINT_CLIENT_CONCEPT_TEST_H_
|
||||
@@ -16,27 +16,32 @@ namespace brave_account::endpoint_client {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Concept that checks whether `T` defines a static, accessible member
|
||||
// function `URL()` such that:
|
||||
// - `T::URL()` is a valid expression,
|
||||
// and that call yields `GURL`
|
||||
//
|
||||
// In short: models any type with a proper static `URL()` function
|
||||
// whose result is a `GURL`.
|
||||
template <typename T>
|
||||
concept URL = requires {
|
||||
{ T::URL() } -> std::same_as<GURL>;
|
||||
// An endpoint defines Request and Response type aliases and a static URL().
|
||||
template <typename Endpoint>
|
||||
concept IsEndpointLike = requires {
|
||||
typename Endpoint::Request;
|
||||
typename Endpoint::Response;
|
||||
{ Endpoint::URL() } -> std::same_as<GURL>;
|
||||
};
|
||||
|
||||
// A JSON endpoint has a JSON request and response type.
|
||||
template <typename Endpoint>
|
||||
concept IsJSONEndpoint =
|
||||
IsEndpointLike<Endpoint> && IsJSONRequest<typename Endpoint::Request> &&
|
||||
IsJSONResponse<typename Endpoint::Response>;
|
||||
|
||||
// A Protobuf endpoint has a Protobuf request and response type.
|
||||
template <typename Endpoint>
|
||||
concept IsProtobufEndpoint =
|
||||
IsEndpointLike<Endpoint> && IsProtobufRequest<typename Endpoint::Request> &&
|
||||
IsProtobufResponse<typename Endpoint::Response>;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
// An endpoint is either a JSON endpoint or a Protobuf endpoint.
|
||||
template <typename Endpoint>
|
||||
concept IsEndpoint =
|
||||
requires {
|
||||
typename T::Request;
|
||||
typename T::Response;
|
||||
} && detail::IsRequest<typename T::Request> &&
|
||||
detail::IsResponse<typename T::Response> && detail::URL<T>;
|
||||
detail::IsJSONEndpoint<Endpoint> || detail::IsProtobufEndpoint<Endpoint>;
|
||||
|
||||
} // namespace brave_account::endpoint_client
|
||||
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/is_endpoint.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_account/endpoint_client/concept_test.h"
|
||||
#include "base/strings/strcat.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_test_endpoint_bodies.h"
|
||||
#include "brave/components/brave_account/endpoint_client/protobuf_test_endpoint_bodies.pb.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
@@ -22,145 +20,228 @@ namespace brave_account::endpoint_client {
|
||||
namespace {
|
||||
|
||||
struct InvalidRequest {};
|
||||
|
||||
struct ValidRequestBody {
|
||||
base::DictValue ToValue() const;
|
||||
};
|
||||
|
||||
using ValidRequest = POST<ValidRequestBody>;
|
||||
|
||||
struct InvalidResponse {};
|
||||
|
||||
struct ValidResponseBody {
|
||||
static std::optional<ValidResponseBody> FromValue(const base::Value&);
|
||||
using ValidJSONRequest = POST<JSONRequestBody>;
|
||||
using ValidJSONResponse = Response<JSONSuccessBody, JSONErrorBody>;
|
||||
|
||||
using ValidProtobufRequest = POST<ProtobufRequestBody>;
|
||||
using ValidProtobufResponse = Response<ProtobufSuccessBody, ProtobufErrorBody>;
|
||||
|
||||
enum class TypeAliasState {
|
||||
kAbsent,
|
||||
kPresentInvalidJSON,
|
||||
kPresentValidJSON,
|
||||
kPresentInvalidProtobuf,
|
||||
kPresentValidProtobuf
|
||||
};
|
||||
|
||||
using ValidResponse = Response<ValidResponseBody, ValidResponseBody>;
|
||||
enum class FunctionState { kAbsent, kPresentInvalid, kPresentValid };
|
||||
|
||||
struct EndpointInvalidRequest {
|
||||
static constexpr char kName[] = "EndpointInvalidRequest";
|
||||
using Request = InvalidRequest;
|
||||
using Response = ValidResponse;
|
||||
static GURL URL();
|
||||
};
|
||||
|
||||
struct EndpointInvalidResponse {
|
||||
static constexpr char kName[] = "EndpointInvalidResponse";
|
||||
using Request = ValidRequest;
|
||||
using Response = InvalidResponse;
|
||||
static GURL URL();
|
||||
};
|
||||
|
||||
struct EndpointInvalidURL {
|
||||
static constexpr char kName[] = "EndpointInvalidURL";
|
||||
using Request = ValidRequest;
|
||||
using Response = ValidResponse;
|
||||
static std::string URL();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using IsEndpointConceptTest = ConceptTest::Fixture<T>;
|
||||
|
||||
// Hand-crafted invalid endpoints.
|
||||
using EndpointTestTypes =
|
||||
testing::Types<std::tuple<EndpointInvalidRequest, std::false_type>,
|
||||
std::tuple<EndpointInvalidResponse, std::false_type>,
|
||||
std::tuple<EndpointInvalidURL, std::false_type>>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TYPED_TEST_SUITE(IsEndpointConceptTest,
|
||||
EndpointTestTypes,
|
||||
ConceptTest::NameGenerator);
|
||||
|
||||
TYPED_TEST(IsEndpointConceptTest, SatisfyConcept) {
|
||||
using TestType = typename TestFixture::TestType;
|
||||
using ExpectedResult = typename TestFixture::ExpectedResult;
|
||||
EXPECT_EQ(IsEndpoint<TestType>, ExpectedResult::value);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template <bool>
|
||||
template <TypeAliasState>
|
||||
struct MaybeRequest {};
|
||||
|
||||
template <>
|
||||
struct MaybeRequest<true> {
|
||||
using Request = ValidRequest;
|
||||
struct MaybeRequest<TypeAliasState::kPresentInvalidJSON> {
|
||||
using Request = InvalidRequest;
|
||||
};
|
||||
|
||||
template <bool>
|
||||
template <>
|
||||
struct MaybeRequest<TypeAliasState::kPresentValidJSON> {
|
||||
using Request = ValidJSONRequest;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MaybeRequest<TypeAliasState::kPresentInvalidProtobuf> {
|
||||
using Request = InvalidRequest;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MaybeRequest<TypeAliasState::kPresentValidProtobuf> {
|
||||
using Request = ValidProtobufRequest;
|
||||
};
|
||||
|
||||
template <TypeAliasState>
|
||||
struct MaybeResponse {};
|
||||
|
||||
template <>
|
||||
struct MaybeResponse<true> {
|
||||
using Response = ValidResponse;
|
||||
struct MaybeResponse<TypeAliasState::kPresentInvalidJSON> {
|
||||
using Response = InvalidResponse;
|
||||
};
|
||||
|
||||
template <bool>
|
||||
template <>
|
||||
struct MaybeResponse<TypeAliasState::kPresentValidJSON> {
|
||||
using Response = ValidJSONResponse;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MaybeResponse<TypeAliasState::kPresentInvalidProtobuf> {
|
||||
using Response = InvalidResponse;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MaybeResponse<TypeAliasState::kPresentValidProtobuf> {
|
||||
using Response = ValidProtobufResponse;
|
||||
};
|
||||
|
||||
template <FunctionState>
|
||||
struct MaybeURL {};
|
||||
|
||||
template <>
|
||||
struct MaybeURL<true> {
|
||||
struct MaybeURL<FunctionState::kPresentInvalid> {
|
||||
static std::string URL();
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MaybeURL<FunctionState::kPresentValid> {
|
||||
static GURL URL();
|
||||
};
|
||||
|
||||
template <bool HasRequest, bool HasResponse, bool HasURL>
|
||||
struct EndpointCase : MaybeRequest<HasRequest>,
|
||||
MaybeResponse<HasResponse>,
|
||||
MaybeURL<HasURL> {
|
||||
static constexpr bool kHasRequest = HasRequest;
|
||||
static constexpr bool kHasResponse = HasResponse;
|
||||
static constexpr bool kHasURL = HasURL;
|
||||
template <TypeAliasState RequestState,
|
||||
TypeAliasState ResponseState,
|
||||
FunctionState URLState>
|
||||
struct IsEndpointTestCase : MaybeRequest<RequestState>,
|
||||
MaybeResponse<ResponseState>,
|
||||
MaybeURL<URLState> {
|
||||
static constexpr TypeAliasState kRequestState = RequestState;
|
||||
static constexpr TypeAliasState kResponseState = ResponseState;
|
||||
static constexpr FunctionState kURLState = URLState;
|
||||
static constexpr bool kSatisfiesConcept =
|
||||
kHasRequest && kHasResponse && kHasURL;
|
||||
((RequestState == TypeAliasState::kPresentValidJSON &&
|
||||
ResponseState == TypeAliasState::kPresentValidJSON) ||
|
||||
(RequestState == TypeAliasState::kPresentValidProtobuf &&
|
||||
ResponseState == TypeAliasState::kPresentValidProtobuf)) &&
|
||||
URLState == FunctionState::kPresentValid;
|
||||
};
|
||||
|
||||
template <typename>
|
||||
struct IsEndpointConceptMatrixTest : testing::Test {};
|
||||
// clang-format off
|
||||
// Test cases: 5 request states x 5 response states x 3 URL states = 75 tests
|
||||
using IsEndpointTestCases = testing::Types<
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kAbsent, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kAbsent, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kAbsent, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentInvalidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentValidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentValidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentValidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentValidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kAbsent, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kAbsent, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kAbsent, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kAbsent, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentInvalidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentValidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentValidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentValidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentValidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidJSON, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kAbsent, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kAbsent, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kAbsent, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentInvalidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentValidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentValidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentValidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentValidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidJSON, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kAbsent, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kAbsent, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kAbsent, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentInvalidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentValidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentValidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentValidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentValidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentInvalidProtobuf, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kAbsent, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kAbsent, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kAbsent, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentInvalidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentInvalidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentValidJSON, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentValidJSON, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentValidJSON, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentInvalidProtobuf, FunctionState::kPresentValid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentValidProtobuf, FunctionState::kAbsent>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentInvalid>,
|
||||
IsEndpointTestCase<TypeAliasState::kPresentValidProtobuf, TypeAliasState::kPresentValidProtobuf, FunctionState::kPresentValid>
|
||||
>;
|
||||
// clang-format on
|
||||
|
||||
// Exhaustive presence/absence matrix.
|
||||
using EndpointMatrix = testing::Types<EndpointCase<false, false, false>,
|
||||
EndpointCase<false, false, true>,
|
||||
EndpointCase<false, true, false>,
|
||||
EndpointCase<false, true, true>,
|
||||
EndpointCase<true, false, false>,
|
||||
EndpointCase<true, false, true>,
|
||||
EndpointCase<true, true, false>,
|
||||
EndpointCase<true, true, true>>;
|
||||
|
||||
struct EndpointName {
|
||||
template <typename T>
|
||||
struct IsEndpointTestCaseName {
|
||||
template <typename IsEndpointTestCase>
|
||||
static std::string GetName(int) {
|
||||
auto name =
|
||||
[](auto... bools) {
|
||||
std::string bits;
|
||||
((bits += (bools ? "1" : "0")), ...);
|
||||
return bits;
|
||||
}(T::kHasRequest, T::kHasResponse, T::kHasURL) +
|
||||
"_Endpoint";
|
||||
auto type_alias_state_to_string = [](TypeAliasState state) {
|
||||
switch (state) {
|
||||
case TypeAliasState::kAbsent:
|
||||
return "Absent";
|
||||
case TypeAliasState::kPresentInvalidJSON:
|
||||
return "InvalidJSON";
|
||||
case TypeAliasState::kPresentValidJSON:
|
||||
return "ValidJSON";
|
||||
case TypeAliasState::kPresentInvalidProtobuf:
|
||||
return "InvalidProtobuf";
|
||||
case TypeAliasState::kPresentValidProtobuf:
|
||||
return "ValidProtobuf";
|
||||
}
|
||||
};
|
||||
|
||||
if (!T::kHasRequest) {
|
||||
name += "NoRequest";
|
||||
}
|
||||
auto function_state_to_string = [](FunctionState state) {
|
||||
switch (state) {
|
||||
case FunctionState::kAbsent:
|
||||
return "Absent";
|
||||
case FunctionState::kPresentInvalid:
|
||||
return "Invalid";
|
||||
case FunctionState::kPresentValid:
|
||||
return "Valid";
|
||||
}
|
||||
};
|
||||
|
||||
if (!T::kHasResponse) {
|
||||
name += "NoResponse";
|
||||
}
|
||||
|
||||
if (!T::kHasURL) {
|
||||
name += "NoURL";
|
||||
}
|
||||
|
||||
return name + "_does" + (T::kSatisfiesConcept ? "" : "_not");
|
||||
return base::StrCat(
|
||||
{"Request_",
|
||||
type_alias_state_to_string(IsEndpointTestCase::kRequestState),
|
||||
"_Response_",
|
||||
type_alias_state_to_string(IsEndpointTestCase::kResponseState),
|
||||
"_URL_", function_state_to_string(IsEndpointTestCase::kURLState),
|
||||
"_does", (IsEndpointTestCase::kSatisfiesConcept ? "" : "_not")});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename>
|
||||
struct IsEndpointTest : testing::Test {};
|
||||
|
||||
} // namespace
|
||||
|
||||
TYPED_TEST_SUITE(IsEndpointConceptMatrixTest, EndpointMatrix, EndpointName);
|
||||
TYPED_TEST_SUITE(IsEndpointTest, IsEndpointTestCases, IsEndpointTestCaseName);
|
||||
|
||||
TYPED_TEST(IsEndpointConceptMatrixTest, SatisfyConcept) {
|
||||
TYPED_TEST(IsEndpointTest, SatisfyConcept) {
|
||||
EXPECT_EQ(IsEndpoint<TypeParam>, TypeParam::kSatisfiesConcept);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,20 +11,29 @@
|
||||
|
||||
namespace brave_account::endpoint_client::detail {
|
||||
|
||||
// Primary template: a type does not satisfy IsRequest unless
|
||||
// matched by the partial specialization below.
|
||||
template <typename>
|
||||
inline constexpr bool kIsRequest = false;
|
||||
inline constexpr bool kIsJSONRequest = false;
|
||||
|
||||
// Partial specialization: Request<T, M> satisfies IsRequest if
|
||||
// T satisfies IsRequestBody.
|
||||
template <IsRequestBody T, Method M>
|
||||
inline constexpr bool kIsRequest<Request<T, M>> = true;
|
||||
template <IsJSONRequestBody RequestBody, Method M>
|
||||
inline constexpr bool kIsJSONRequest<Request<RequestBody, M>> = true;
|
||||
|
||||
// Concept: a type satisfies IsRequest if
|
||||
// its kIsRequest specialization evaluates to true.
|
||||
template <typename T>
|
||||
concept IsRequest = kIsRequest<T>;
|
||||
// A JSON request is a Request<> whose body is a JSON request body.
|
||||
template <typename Request>
|
||||
concept IsJSONRequest = kIsJSONRequest<Request>;
|
||||
|
||||
template <typename>
|
||||
inline constexpr bool kIsProtobufRequest = false;
|
||||
|
||||
template <IsProtobufRequestBody RequestBody, Method M>
|
||||
inline constexpr bool kIsProtobufRequest<Request<RequestBody, M>> = true;
|
||||
|
||||
// A Protobuf request is a Request<> whose body is a Protobuf request body.
|
||||
template <typename Request>
|
||||
concept IsProtobufRequest = kIsProtobufRequest<Request>;
|
||||
|
||||
// A request is either a JSON request or a Protobuf request.
|
||||
template <typename Request>
|
||||
concept IsRequest = IsJSONRequest<Request> || IsProtobufRequest<Request>;
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
|
||||
@@ -10,22 +10,27 @@
|
||||
#include <type_traits>
|
||||
|
||||
#include "base/values.h"
|
||||
#include "google/protobuf/message_lite.h"
|
||||
|
||||
namespace brave_account::endpoint_client::detail {
|
||||
|
||||
// Concept that checks whether `T` defines a non-static, accessible member
|
||||
// function `ToValue()` such that:
|
||||
// - `t.ToValue()` is a valid expression,
|
||||
// and that call yields a `base::DictValue`
|
||||
// - `&T::ToValue` is a valid member function pointer (ensures it's a
|
||||
// non-static member function)
|
||||
//
|
||||
// In short: models any type with a proper non-static `ToValue()` function
|
||||
// whose result is a `base::DictValue`.
|
||||
template <typename T>
|
||||
concept IsRequestBody = requires(T t) {
|
||||
{ t.ToValue() } -> std::same_as<base::DictValue>;
|
||||
} && std::is_member_function_pointer_v<decltype(&T::ToValue)>;
|
||||
// A JSON request body provides a non-static ToValue()
|
||||
// callable on a const object and returning base::DictValue.
|
||||
template <typename RequestBody>
|
||||
concept IsJSONRequestBody = requires(const RequestBody& request_body) {
|
||||
{ request_body.ToValue() } -> std::same_as<base::DictValue>;
|
||||
} && std::is_member_function_pointer_v<decltype(&RequestBody::ToValue)>;
|
||||
|
||||
// A Protobuf request body is a type publicly derived from
|
||||
// google::protobuf::MessageLite.
|
||||
template <typename RequestBody>
|
||||
concept IsProtobufRequestBody =
|
||||
std::derived_from<RequestBody, google::protobuf::MessageLite>;
|
||||
|
||||
// A request body is either a JSON request body or a Protobuf request body.
|
||||
template <typename RequestBody>
|
||||
concept IsRequestBody =
|
||||
IsJSONRequestBody<RequestBody> || IsProtobufRequestBody<RequestBody>;
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
|
||||
@@ -5,61 +5,96 @@
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/is_request_body.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_account/endpoint_client/concept_test.h"
|
||||
#include "base/strings/strcat.h"
|
||||
#include "google/protobuf/message_lite.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace base {
|
||||
class DictValue;
|
||||
} // namespace base
|
||||
|
||||
namespace brave_account::endpoint_client::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
struct RequestBodyNoToValue {
|
||||
static constexpr char kName[] = "RequestBodyNoToValue";
|
||||
struct JSONRequestBodyNoToValue {
|
||||
static constexpr char kName[] = "JSONRequestBodyNoToValue";
|
||||
};
|
||||
|
||||
struct RequestBodyStaticToValue {
|
||||
static constexpr char kName[] = "RequestBodyStaticToValue";
|
||||
struct JSONRequestBodyStaticToValue {
|
||||
static constexpr char kName[] = "JSONRequestBodyStaticToValue";
|
||||
static base::DictValue ToValue();
|
||||
};
|
||||
|
||||
struct RequestBodyToValueWithWrongReturnType {
|
||||
static constexpr char kName[] = "RequestBodyToValueWithWrongReturnType";
|
||||
void ToValue() const;
|
||||
struct JSONRequestBodyNonConstToValue {
|
||||
static constexpr char kName[] = "JSONRequestBodyNonConstToValue";
|
||||
base::DictValue ToValue();
|
||||
};
|
||||
|
||||
struct RequestBodyToValueWithWrongParameterType {
|
||||
static constexpr char kName[] = "RequestBodyToValueWithWrongParameterType";
|
||||
struct JSONRequestBodyToValueWithWrongReturnType {
|
||||
static constexpr char kName[] = "JSONRequestBodyToValueWithWrongReturnType";
|
||||
std::string ToValue() const;
|
||||
};
|
||||
|
||||
struct JSONRequestBodyToValueWithWrongParameterType {
|
||||
static constexpr char kName[] =
|
||||
"JSONRequestBodyToValueWithWrongParameterType";
|
||||
base::DictValue ToValue(int) const;
|
||||
};
|
||||
|
||||
struct ValidRequestBody {
|
||||
static constexpr char kName[] = "ValidRequestBody";
|
||||
struct JSONRequestBodyValid {
|
||||
static constexpr char kName[] = "JSONRequestBodyValid";
|
||||
base::DictValue ToValue() const;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using IsRequestBodyConceptTest = ConceptTest::Fixture<T>;
|
||||
class ProtobufRequestBodyPrivateInheritance : google::protobuf::MessageLite {
|
||||
public:
|
||||
static constexpr char kName[] = "ProtobufRequestBodyPrivateInheritance";
|
||||
};
|
||||
|
||||
using RequestBodyTestTypes = testing::Types<
|
||||
std::tuple<RequestBodyNoToValue, std::false_type>,
|
||||
std::tuple<RequestBodyStaticToValue, std::false_type>,
|
||||
std::tuple<RequestBodyToValueWithWrongReturnType, std::false_type>,
|
||||
std::tuple<RequestBodyToValueWithWrongParameterType, std::false_type>,
|
||||
std::tuple<ValidRequestBody, std::true_type>>;
|
||||
struct ProtobufRequestBodyValid : google::protobuf::MessageLite {
|
||||
static constexpr char kName[] = "ProtobufRequestBodyValid";
|
||||
};
|
||||
|
||||
template <typename T, bool SatisfiesConcept>
|
||||
struct IsRequestBodyTestCase {
|
||||
using Type = T;
|
||||
static constexpr bool kSatisfiesConcept = SatisfiesConcept;
|
||||
};
|
||||
|
||||
using IsRequestBodyTestCases = testing::Types<
|
||||
IsRequestBodyTestCase<JSONRequestBodyNoToValue, false>,
|
||||
IsRequestBodyTestCase<JSONRequestBodyStaticToValue, false>,
|
||||
IsRequestBodyTestCase<JSONRequestBodyNonConstToValue, false>,
|
||||
IsRequestBodyTestCase<JSONRequestBodyToValueWithWrongReturnType, false>,
|
||||
IsRequestBodyTestCase<JSONRequestBodyToValueWithWrongParameterType, false>,
|
||||
IsRequestBodyTestCase<JSONRequestBodyValid, true>,
|
||||
IsRequestBodyTestCase<ProtobufRequestBodyPrivateInheritance, false>,
|
||||
IsRequestBodyTestCase<ProtobufRequestBodyValid, true>>;
|
||||
|
||||
struct IsRequestBodyTestCaseName {
|
||||
template <typename IsRequestBodyTestCase>
|
||||
static std::string GetName(int) {
|
||||
return base::StrCat(
|
||||
{IsRequestBodyTestCase::Type::kName, "_does",
|
||||
(IsRequestBodyTestCase::kSatisfiesConcept ? "" : "_not")});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename>
|
||||
struct IsRequestBodyTest : testing::Test {};
|
||||
|
||||
} // namespace
|
||||
|
||||
TYPED_TEST_SUITE(IsRequestBodyConceptTest,
|
||||
RequestBodyTestTypes,
|
||||
ConceptTest::NameGenerator);
|
||||
TYPED_TEST_SUITE(IsRequestBodyTest,
|
||||
IsRequestBodyTestCases,
|
||||
IsRequestBodyTestCaseName);
|
||||
|
||||
TYPED_TEST(IsRequestBodyConceptTest, SatisfyConcept) {
|
||||
using TestType = typename TestFixture::TestType;
|
||||
using ExpectedResult = typename TestFixture::ExpectedResult;
|
||||
EXPECT_EQ(IsRequestBody<TestType>, ExpectedResult::value);
|
||||
TYPED_TEST(IsRequestBodyTest, SatisfyConcept) {
|
||||
EXPECT_EQ(IsRequestBody<typename TypeParam::Type>,
|
||||
TypeParam::kSatisfiesConcept);
|
||||
}
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
@@ -5,11 +5,8 @@
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/is_request.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_account/endpoint_client/concept_test.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_test_endpoint_bodies.h"
|
||||
#include "brave/components/brave_account/endpoint_client/protobuf_test_endpoint_bodies.pb.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
@@ -17,36 +14,47 @@ namespace brave_account::endpoint_client::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
struct ValidRequestBody {
|
||||
base::DictValue ToValue() const;
|
||||
template <typename T, bool SatisfiesConcept>
|
||||
struct IsRequestTestCase {
|
||||
using Type = T;
|
||||
static constexpr bool kSatisfiesConcept = SatisfiesConcept;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using IsRequestConceptTest = ConceptTest::Fixture<T>;
|
||||
using IsRequestTestCases =
|
||||
testing::Types<IsRequestTestCase<void*, false>,
|
||||
IsRequestTestCase<volatile int, false>,
|
||||
IsRequestTestCase<JSONRequestBody, false>,
|
||||
IsRequestTestCase<CONNECT<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<DELETE<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<GET<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<HEAD<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<OPTIONS<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<PATCH<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<POST<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<PUT<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<TRACE<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<TRACK<JSONRequestBody>, true>,
|
||||
IsRequestTestCase<ProtobufRequestBody, false>,
|
||||
IsRequestTestCase<CONNECT<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<DELETE<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<GET<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<HEAD<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<OPTIONS<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<PATCH<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<POST<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<PUT<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<TRACE<ProtobufRequestBody>, true>,
|
||||
IsRequestTestCase<TRACK<ProtobufRequestBody>, true>>;
|
||||
|
||||
using RequestTestTypes =
|
||||
testing::Types<std::tuple<void*, std::false_type>,
|
||||
std::tuple<volatile int, std::false_type>,
|
||||
std::tuple<ValidRequestBody, std::false_type>,
|
||||
std::tuple<CONNECT<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<DELETE<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<GET<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<HEAD<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<OPTIONS<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<PATCH<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<POST<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<PUT<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<TRACE<ValidRequestBody>, std::true_type>,
|
||||
std::tuple<TRACK<ValidRequestBody>, std::true_type>>;
|
||||
template <typename>
|
||||
struct IsRequestTest : testing::Test {};
|
||||
|
||||
} // namespace
|
||||
|
||||
TYPED_TEST_SUITE(IsRequestConceptTest, RequestTestTypes);
|
||||
TYPED_TEST_SUITE(IsRequestTest, IsRequestTestCases);
|
||||
|
||||
TYPED_TEST(IsRequestConceptTest, SatisfyConcept) {
|
||||
using TestType = typename TestFixture::TestType;
|
||||
using ExpectedResult = typename TestFixture::ExpectedResult;
|
||||
EXPECT_EQ(IsRequest<TestType>, ExpectedResult::value);
|
||||
TYPED_TEST(IsRequestTest, SatisfyConcept) {
|
||||
EXPECT_EQ(IsRequest<typename TypeParam::Type>, TypeParam::kSatisfiesConcept);
|
||||
}
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
@@ -11,20 +11,31 @@
|
||||
|
||||
namespace brave_account::endpoint_client::detail {
|
||||
|
||||
// Primary template: a type does not satisfy IsResponse unless
|
||||
// matched by the partial specialization below.
|
||||
template <typename>
|
||||
inline constexpr bool kIsResponse = false;
|
||||
inline constexpr bool kIsJSONResponse = false;
|
||||
|
||||
// Partial specialization: Response<T, E> satisfies IsResponse if
|
||||
// T and E satisfy IsResponseBody.
|
||||
template <IsResponseBody T, IsResponseBody E>
|
||||
inline constexpr bool kIsResponse<Response<T, E>> = true;
|
||||
template <IsJSONResponseBody SuccessBody, IsJSONResponseBody ErrorBody>
|
||||
inline constexpr bool kIsJSONResponse<Response<SuccessBody, ErrorBody>> = true;
|
||||
|
||||
// Concept: a type satisfies IsResponse if
|
||||
// its kIsResponse specialization evaluates to true.
|
||||
template <typename T>
|
||||
concept IsResponse = kIsResponse<T>;
|
||||
// A JSON response is a Response<> whose bodies are JSON response bodies.
|
||||
template <typename Response>
|
||||
concept IsJSONResponse = kIsJSONResponse<Response>;
|
||||
|
||||
template <typename>
|
||||
inline constexpr bool kIsProtobufResponse = false;
|
||||
|
||||
template <IsProtobufResponseBody SuccessBody, IsProtobufResponseBody ErrorBody>
|
||||
inline constexpr bool kIsProtobufResponse<Response<SuccessBody, ErrorBody>> =
|
||||
true;
|
||||
|
||||
// A Protobuf response is a Response<> whose bodies are Protobuf response
|
||||
// bodies.
|
||||
template <typename Response>
|
||||
concept IsProtobufResponse = kIsProtobufResponse<Response>;
|
||||
|
||||
// A response is either a JSON response or a Protobuf response.
|
||||
template <typename Response>
|
||||
concept IsResponse = IsJSONResponse<Response> || IsProtobufResponse<Response>;
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
|
||||
@@ -9,24 +9,31 @@
|
||||
#include <concepts>
|
||||
#include <optional>
|
||||
|
||||
namespace base {
|
||||
class Value;
|
||||
} // namespace base
|
||||
#include "base/values.h"
|
||||
#include "google/protobuf/message_lite.h"
|
||||
|
||||
namespace brave_account::endpoint_client::detail {
|
||||
|
||||
// Concept that checks whether `T` defines a static, accessible member
|
||||
// function `FromValue()` such that:
|
||||
// - `T::FromValue(value)` is a valid expression,
|
||||
// and that call yields `std::optional<T>`
|
||||
//
|
||||
// In short: models any type with a proper static `FromValue()` function
|
||||
// whose result is a `std::optional<T>`.
|
||||
template <typename T>
|
||||
concept IsResponseBody = requires(const base::Value& value) {
|
||||
{ T::FromValue(value) } -> std::same_as<std::optional<T>>;
|
||||
// A JSON response body provides a static FromValue() taking
|
||||
// const base::Value& and returning std::optional<ResponseBody>.
|
||||
template <typename ResponseBody>
|
||||
concept IsJSONResponseBody = requires(const base::Value& value) {
|
||||
{
|
||||
ResponseBody::FromValue(value)
|
||||
} -> std::same_as<std::optional<ResponseBody>>;
|
||||
};
|
||||
|
||||
// A Protobuf response body is a type publicly derived from
|
||||
// google::protobuf::MessageLite.
|
||||
template <typename ResponseBody>
|
||||
concept IsProtobufResponseBody =
|
||||
std::derived_from<ResponseBody, google::protobuf::MessageLite>;
|
||||
|
||||
// A response body is either a JSON response body or a Protobuf response body.
|
||||
template <typename ResponseBody>
|
||||
concept IsResponseBody =
|
||||
IsJSONResponseBody<ResponseBody> || IsProtobufResponseBody<ResponseBody>;
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
#endif // BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINT_CLIENT_IS_RESPONSE_BODY_H_
|
||||
|
||||
@@ -6,62 +6,95 @@
|
||||
#include "brave/components/brave_account/endpoint_client/is_response_body.h"
|
||||
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_account/endpoint_client/concept_test.h"
|
||||
#include "base/strings/strcat.h"
|
||||
#include "google/protobuf/message_lite.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace base {
|
||||
class Value;
|
||||
} // namespace base
|
||||
|
||||
namespace brave_account::endpoint_client::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
struct ResponseBodyNoFromValue {
|
||||
static constexpr char kName[] = "ResponseBodyNoFromValue";
|
||||
struct JSONResponseBodyNoFromValue {
|
||||
static constexpr char kName[] = "JSONResponseBodyNoFromValue";
|
||||
};
|
||||
|
||||
struct ResponseBodyNonStaticFromValue {
|
||||
static constexpr char kName[] = "ResponseBodyNonStaticFromValue";
|
||||
std::optional<ResponseBodyNonStaticFromValue> FromValue(const base::Value&);
|
||||
struct JSONResponseBodyNonStaticFromValue {
|
||||
static constexpr char kName[] = "JSONResponseBodyNonStaticFromValue";
|
||||
std::optional<JSONResponseBodyNonStaticFromValue> FromValue(
|
||||
const base::Value&);
|
||||
};
|
||||
|
||||
struct ResponseBodyFromValueWithWrongReturnType {
|
||||
static constexpr char kName[] = "ResponseBodyFromValueWithWrongReturnType";
|
||||
static ResponseBodyFromValueWithWrongReturnType FromValue(const base::Value&);
|
||||
struct JSONResponseBodyFromValueWithWrongReturnType {
|
||||
static constexpr char kName[] =
|
||||
"JSONResponseBodyFromValueWithWrongReturnType";
|
||||
static JSONResponseBodyFromValueWithWrongReturnType FromValue(
|
||||
const base::Value&);
|
||||
};
|
||||
|
||||
struct ResponseBodyFromValueWithWrongParameterType {
|
||||
static constexpr char kName[] = "ResponseBodyFromValueWithWrongParameterType";
|
||||
static std::optional<ResponseBodyFromValueWithWrongParameterType> FromValue(
|
||||
int);
|
||||
struct JSONResponseBodyFromValueWithWrongParameterType {
|
||||
static constexpr char kName[] =
|
||||
"JSONResponseBodyFromValueWithWrongParameterType";
|
||||
static std::optional<JSONResponseBodyFromValueWithWrongParameterType>
|
||||
FromValue(const std::string&);
|
||||
};
|
||||
|
||||
struct ValidResponseBody {
|
||||
static constexpr char kName[] = "ValidResponseBody";
|
||||
static std::optional<ValidResponseBody> FromValue(const base::Value&);
|
||||
struct JSONResponseBodyValid {
|
||||
static constexpr char kName[] = "JSONResponseBodyValid";
|
||||
static std::optional<JSONResponseBodyValid> FromValue(const base::Value&);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using IsResponseBodyConceptTest = ConceptTest::Fixture<T>;
|
||||
class ProtobufResponseBodyPrivateInheritance : google::protobuf::MessageLite {
|
||||
public:
|
||||
static constexpr char kName[] = "ProtobufResponseBodyPrivateInheritance";
|
||||
};
|
||||
|
||||
using ResponseBodyTestTypes = testing::Types<
|
||||
std::tuple<ResponseBodyNoFromValue, std::false_type>,
|
||||
std::tuple<ResponseBodyNonStaticFromValue, std::false_type>,
|
||||
std::tuple<ResponseBodyFromValueWithWrongReturnType, std::false_type>,
|
||||
std::tuple<ResponseBodyFromValueWithWrongParameterType, std::false_type>,
|
||||
std::tuple<ValidResponseBody, std::true_type>>;
|
||||
struct ProtobufResponseBodyValid : google::protobuf::MessageLite {
|
||||
static constexpr char kName[] = "ProtobufResponseBodyValid";
|
||||
};
|
||||
|
||||
template <typename T, bool SatisfiesConcept>
|
||||
struct IsResponseBodyTestCase {
|
||||
using Type = T;
|
||||
static constexpr bool kSatisfiesConcept = SatisfiesConcept;
|
||||
};
|
||||
|
||||
using IsResponseBodyTestCases = testing::Types<
|
||||
IsResponseBodyTestCase<JSONResponseBodyNoFromValue, false>,
|
||||
IsResponseBodyTestCase<JSONResponseBodyNonStaticFromValue, false>,
|
||||
IsResponseBodyTestCase<JSONResponseBodyFromValueWithWrongReturnType, false>,
|
||||
IsResponseBodyTestCase<JSONResponseBodyFromValueWithWrongParameterType,
|
||||
false>,
|
||||
IsResponseBodyTestCase<JSONResponseBodyValid, true>,
|
||||
IsResponseBodyTestCase<ProtobufResponseBodyPrivateInheritance, false>,
|
||||
IsResponseBodyTestCase<ProtobufResponseBodyValid, true>>;
|
||||
|
||||
struct IsResponseBodyTestCaseName {
|
||||
template <typename IsResponseBodyTestCase>
|
||||
static std::string GetName(int) {
|
||||
return base::StrCat(
|
||||
{IsResponseBodyTestCase::Type::kName, "_does",
|
||||
(IsResponseBodyTestCase::kSatisfiesConcept ? "" : "_not")});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename>
|
||||
struct IsResponseBodyTest : testing::Test {};
|
||||
|
||||
} // namespace
|
||||
|
||||
TYPED_TEST_SUITE(IsResponseBodyConceptTest,
|
||||
ResponseBodyTestTypes,
|
||||
ConceptTest::NameGenerator);
|
||||
TYPED_TEST_SUITE(IsResponseBodyTest,
|
||||
IsResponseBodyTestCases,
|
||||
IsResponseBodyTestCaseName);
|
||||
|
||||
TYPED_TEST(IsResponseBodyConceptTest, SatisfyConcept) {
|
||||
using TestType = typename TestFixture::TestType;
|
||||
using ExpectedResult = typename TestFixture::ExpectedResult;
|
||||
EXPECT_EQ(IsResponseBody<TestType>, ExpectedResult::value);
|
||||
TYPED_TEST(IsResponseBodyTest, SatisfyConcept) {
|
||||
EXPECT_EQ(IsResponseBody<typename TypeParam::Type>,
|
||||
TypeParam::kSatisfiesConcept);
|
||||
}
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
@@ -5,12 +5,8 @@
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/is_response.h"
|
||||
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_account/endpoint_client/concept_test.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_test_endpoint_bodies.h"
|
||||
#include "brave/components/brave_account/endpoint_client/protobuf_test_endpoint_bodies.pb.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
@@ -18,27 +14,30 @@ namespace brave_account::endpoint_client::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
struct ValidResponseBody {
|
||||
static std::optional<ValidResponseBody> FromValue(const base::Value&);
|
||||
template <typename T, bool SatisfiesConcept>
|
||||
struct IsResponseTestCase {
|
||||
using Type = T;
|
||||
static constexpr bool kSatisfiesConcept = SatisfiesConcept;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using IsResponseConceptTest = ConceptTest::Fixture<T>;
|
||||
using IsResponseTestCases = testing::Types<
|
||||
IsResponseTestCase<void*, false>,
|
||||
IsResponseTestCase<volatile int, false>,
|
||||
IsResponseTestCase<JSONSuccessBody, false>,
|
||||
IsResponseTestCase<Response<JSONSuccessBody, JSONErrorBody>, true>,
|
||||
IsResponseTestCase<ProtobufSuccessBody, false>,
|
||||
IsResponseTestCase<Response<ProtobufSuccessBody, ProtobufErrorBody>, true>,
|
||||
IsResponseTestCase<Response<JSONSuccessBody, ProtobufErrorBody>, false>>;
|
||||
|
||||
using ResponseTestTypes = testing::Types<
|
||||
std::tuple<void*, std::false_type>,
|
||||
std::tuple<volatile int, std::false_type>,
|
||||
std::tuple<ValidResponseBody, std::false_type>,
|
||||
std::tuple<Response<ValidResponseBody, ValidResponseBody>, std::true_type>>;
|
||||
template <typename>
|
||||
struct IsResponseTest : testing::Test {};
|
||||
|
||||
} // namespace
|
||||
|
||||
TYPED_TEST_SUITE(IsResponseConceptTest, ResponseTestTypes);
|
||||
TYPED_TEST_SUITE(IsResponseTest, IsResponseTestCases);
|
||||
|
||||
TYPED_TEST(IsResponseConceptTest, SatisfyConcept) {
|
||||
using TestType = typename TestFixture::TestType;
|
||||
using ExpectedResult = typename TestFixture::ExpectedResult;
|
||||
EXPECT_EQ(IsResponse<TestType>, ExpectedResult::value);
|
||||
TYPED_TEST(IsResponseTest, SatisfyConcept) {
|
||||
EXPECT_EQ(IsResponse<typename TypeParam::Type>, TypeParam::kSatisfiesConcept);
|
||||
}
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
+4
-7
@@ -3,11 +3,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Schema for the /v2/auth/logout request and response.
|
||||
namespace endpoints {
|
||||
dictionary AuthLogoutRequestBody {};
|
||||
|
||||
dictionary AuthLogoutSuccessBody {};
|
||||
|
||||
dictionary AuthLogoutErrorBody {};
|
||||
// Schema for the empty JSON body.
|
||||
// Used when no request body is sent or the response body is ignored.
|
||||
namespace endpoint_client {
|
||||
dictionary JSONEmptyBody {};
|
||||
};
|
||||
+11
-5
@@ -3,11 +3,17 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Schema for the /v2/verify request and response.
|
||||
namespace endpoints {
|
||||
dictionary VerifyDeleteRequestBody {};
|
||||
// Schemas for JSON bodies used by test endpoints.
|
||||
namespace endpoint_client {
|
||||
dictionary JSONRequestBody {
|
||||
DOMString request;
|
||||
};
|
||||
|
||||
dictionary VerifyDeleteSuccessBody {};
|
||||
dictionary JSONSuccessBody {
|
||||
DOMString success;
|
||||
};
|
||||
|
||||
dictionary VerifyDeleteErrorBody {};
|
||||
dictionary JSONErrorBody {
|
||||
DOMString error;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
package brave_account.endpoint_client;
|
||||
|
||||
// Schema for the empty Protobuf body.
|
||||
// Used when no request body is sent or the response body is ignored.
|
||||
// Compatible with LITE_RUNTIME because it subclasses MessageLite
|
||||
// (unlike google::protobuf::Empty).
|
||||
message ProtobufEmptyBody {}
|
||||
@@ -0,0 +1,24 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
package brave_account.endpoint_client;
|
||||
|
||||
// Schemas for Protobuf bodies used by test endpoints.
|
||||
|
||||
message ProtobufRequestBody {
|
||||
string request = 1;
|
||||
}
|
||||
|
||||
message ProtobufSuccessBody {
|
||||
string success = 1;
|
||||
}
|
||||
|
||||
message ProtobufErrorBody {
|
||||
string error = 1;
|
||||
}
|
||||
@@ -6,9 +6,13 @@
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINT_CLIENT_REQUEST_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINT_CLIENT_REQUEST_H_
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "base/json/json_writer.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_account/endpoint_client/is_request_body.h"
|
||||
#include "net/http/http_request_headers.h"
|
||||
#include "net/traffic_annotation/network_traffic_annotation.h"
|
||||
@@ -29,11 +33,12 @@ enum class Method {
|
||||
kTrack
|
||||
};
|
||||
|
||||
// Wrapper that binds an IsRequestBody to a specific HTTP method.
|
||||
// Inherits from T to expose its ToValue() interface, and
|
||||
// adds a static Method() accessor returning the canonical HTTP method string.
|
||||
template <IsRequestBody T, Method M>
|
||||
struct Request : T {
|
||||
// A request binds a request body type to a specific HTTP method and
|
||||
// provides access to the method, Content-Type, and serialized body.
|
||||
template <IsRequestBody RequestBody, Method M>
|
||||
struct Request {
|
||||
using Body = RequestBody;
|
||||
|
||||
static constexpr std::string_view Method() {
|
||||
if constexpr (M == Method::kConnect) {
|
||||
return net::HttpRequestHeaders::kConnectMethod;
|
||||
@@ -60,8 +65,43 @@ struct Request : T {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::string_view ContentType() {
|
||||
return ContentType<Body>();
|
||||
}
|
||||
|
||||
std::optional<std::string> Serialize() const { return Serialize(body); }
|
||||
|
||||
Body body;
|
||||
net::MutableNetworkTrafficAnnotationTag network_traffic_annotation_tag;
|
||||
base::TimeDelta timeout_duration;
|
||||
|
||||
private:
|
||||
// Returns the Content-Type value for a JSON request body.
|
||||
template <IsJSONRequestBody>
|
||||
static constexpr std::string_view ContentType() {
|
||||
return "application/json";
|
||||
}
|
||||
|
||||
// Returns the Content-Type value for a Protobuf request body.
|
||||
template <IsProtobufRequestBody>
|
||||
static constexpr std::string_view ContentType() {
|
||||
return "application/protobuf";
|
||||
}
|
||||
|
||||
// Serializes a JSON request body to a JSON string.
|
||||
// Returns std::nullopt if the request body produces an empty dictionary.
|
||||
static auto Serialize(const IsJSONRequestBody auto& request_body) {
|
||||
const base::DictValue dict = request_body.ToValue();
|
||||
return !dict.empty() ? base::WriteJson(dict).value_or("")
|
||||
: std::optional<std::string>();
|
||||
}
|
||||
|
||||
// Serializes a Protobuf request body to a binary string.
|
||||
// Returns std::nullopt if the request body is empty (ByteSizeLong() == 0).
|
||||
static auto Serialize(const IsProtobufRequestBody auto& request_body) {
|
||||
return request_body.ByteSizeLong() ? request_body.SerializeAsString()
|
||||
: std::optional<std::string>();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace brave_account::endpoint_client::detail
|
||||
|
||||
@@ -6,22 +6,132 @@
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINT_CLIENT_RESPONSE_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINT_CLIENT_RESPONSE_H_
|
||||
|
||||
#include <concepts>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/json/json_reader.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/types/expected.h"
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_account/endpoint_client/is_response_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_empty_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/protobuf_empty_body.pb.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "services/network/public/cpp/header_util.h"
|
||||
|
||||
namespace brave_account::endpoint_client {
|
||||
|
||||
template <detail::IsResponseBody T, detail::IsResponseBody E>
|
||||
// A response contains the network result, HTTP status code, and the
|
||||
// deserialized success or error body.
|
||||
template <detail::IsResponseBody SuccessResponseBody,
|
||||
detail::IsResponseBody ErrorResponseBody>
|
||||
struct Response {
|
||||
using SuccessBody = T;
|
||||
using ErrorBody = E;
|
||||
using SuccessBody = SuccessResponseBody;
|
||||
using ErrorBody = ErrorResponseBody;
|
||||
|
||||
// Builds a Response from the network result, HTTP response headers,
|
||||
// and optional response body. Body parsing is only attempted for
|
||||
// net::OK responses with headers present.
|
||||
static Response Deserialize(
|
||||
int net_error,
|
||||
const scoped_refptr<net::HttpResponseHeaders>& headers,
|
||||
std::optional<std::string> response_body) {
|
||||
Response response;
|
||||
response.net_error = net_error;
|
||||
if (response.net_error != net::OK || !headers) {
|
||||
return response;
|
||||
}
|
||||
|
||||
response.status_code = headers->response_code();
|
||||
if (network::IsSuccessfulStatus(*response.status_code)) { // 2xx
|
||||
response.body = Deserialize<SuccessBody>(std::move(response_body));
|
||||
} else { // non-2xx
|
||||
response.body = Deserialize<ErrorBody>(std::move(response_body))
|
||||
.transform([](auto error_body) {
|
||||
return base::unexpected(std::move(error_body));
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
int net_error = net::ERR_IO_PENDING;
|
||||
std::optional<int> status_code;
|
||||
std::optional<base::expected<T, E>> body;
|
||||
std::optional<base::expected<SuccessBody, ErrorBody>> body;
|
||||
|
||||
private:
|
||||
// Deserializes a JSON response body into ResponseBody.
|
||||
//
|
||||
// Behavior:
|
||||
//
|
||||
// 1. No body expected:
|
||||
// (std::same_as<ResponseBody, JSONEmptyBody>):
|
||||
// Whatever the server returns is ignored. We replace it with "{}" so the
|
||||
// returned value is always a valid instance of the empty type, never
|
||||
// std::nullopt. This also means parsing failures are ignored: if you don't
|
||||
// expect data from the server, you shouldn't care whether what it sent was
|
||||
// parseable or not.
|
||||
//
|
||||
// 2. Body expected:
|
||||
// (!std::same_as<ResponseBody, JSONEmptyBody>):
|
||||
// The returned value is std::nullopt if:
|
||||
// - response_body is std::nullopt,
|
||||
// - JSONReader::Read() fails,
|
||||
// - ResponseBody::FromValue() fails.
|
||||
//
|
||||
// The returned value is non-std::nullopt only when response_body contains
|
||||
// valid JSON matching the expected ResponseBody structure.
|
||||
template <detail::IsJSONResponseBody ResponseBody>
|
||||
static auto Deserialize(std::optional<std::string> response_body) {
|
||||
if (std::same_as<ResponseBody, JSONEmptyBody>) {
|
||||
response_body = "{}";
|
||||
}
|
||||
|
||||
const auto value =
|
||||
base::JSONReader::Read(response_body.value_or(""), base::JSON_PARSE_RFC)
|
||||
.value_or(base::Value());
|
||||
|
||||
return ResponseBody::FromValue(value);
|
||||
}
|
||||
|
||||
// Deserializes a Protobuf response body into ResponseBody.
|
||||
//
|
||||
// Behavior:
|
||||
//
|
||||
// 1. No body expected
|
||||
// (std::same_as<ResponseBody, ProtobufEmptyBody>):
|
||||
// Whatever the server returns is ignored. We skip parsing and treat the
|
||||
// body as successfully deserialized, so the returned value is always a
|
||||
// valid instance of the empty type, never std::nullopt. This also means
|
||||
// parsing failures are ignored: if you don't expect
|
||||
// data from the server, you shouldn't care whether what it sent was
|
||||
// parseable or not.
|
||||
//
|
||||
// 2. Body expected
|
||||
// (!std::same_as<ResponseBody, ProtobufEmptyBody>):
|
||||
// The returned value is std::nullopt if:
|
||||
// - response_body is std::nullopt,
|
||||
// - response_body is empty,
|
||||
// - parsing via ParseFromString() fails (invalid wire format).
|
||||
//
|
||||
// The returned value is non-std::nullopt only when response_body is
|
||||
// non-empty and can be successfully parsed into ResponseBody.
|
||||
//
|
||||
// Note: successful parsing guarantees only wire-format compatibility with
|
||||
// ResponseBody. Protobuf permits unknown fields, so semantic correctness
|
||||
// must be validated by the caller.
|
||||
template <detail::IsProtobufResponseBody ResponseBody>
|
||||
static auto Deserialize(std::optional<std::string> response_body) {
|
||||
ResponseBody body;
|
||||
return std::same_as<ResponseBody, ProtobufEmptyBody> ||
|
||||
(response_body && !response_body->empty() &&
|
||||
body.ParseFromString(*response_body))
|
||||
? std::optional(std::move(body))
|
||||
: std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace brave_account::endpoint_client
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "net/http/http_status_code.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "services/network/public/cpp/url_loader_completion_status.h"
|
||||
#include "services/network/public/mojom/url_response_head.mojom.h"
|
||||
#include "services/network/test/test_url_loader_factory.h"
|
||||
#include "services/network/test/test_utils.h"
|
||||
|
||||
@@ -31,7 +32,7 @@ void MockResponseFor(network::TestURLLoaderFactory& test_url_loader_factory,
|
||||
return network::CreateURLResponseHead(
|
||||
static_cast<net::HttpStatusCode>(status_code));
|
||||
})
|
||||
.value_or(nullptr);
|
||||
.value_or(network::mojom::URLResponseHead::New());
|
||||
|
||||
const auto body = base::WriteJson(response.body
|
||||
.transform([](const auto& body) {
|
||||
|
||||
@@ -27,7 +27,6 @@ source_set("endpoints") {
|
||||
|
||||
generated_types("generated_api_types") {
|
||||
sources = [
|
||||
"auth_logout_bodies.idl",
|
||||
"auth_validate_bodies.idl",
|
||||
"error_body.idl",
|
||||
"login_finalize_bodies.idl",
|
||||
@@ -35,7 +34,6 @@ generated_types("generated_api_types") {
|
||||
"password_finalize_bodies.idl",
|
||||
"password_init_bodies.idl",
|
||||
"service_token_bodies.idl",
|
||||
"verify_delete_bodies.idl",
|
||||
"verify_resend_bodies.idl",
|
||||
"verify_result_bodies.idl",
|
||||
]
|
||||
|
||||
@@ -7,17 +7,18 @@
|
||||
#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINTS_AUTH_LOGOUT_H_
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/brave_endpoint.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_empty_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "brave/components/brave_account/endpoints/auth_logout_bodies.h"
|
||||
|
||||
namespace brave_account::endpoints {
|
||||
|
||||
using AuthLogout = endpoint_client::BraveEndpoint<
|
||||
"accounts.bsg",
|
||||
"/v2/auth/logout",
|
||||
endpoint_client::POST<AuthLogoutRequestBody>,
|
||||
endpoint_client::Response<AuthLogoutSuccessBody, AuthLogoutErrorBody>>;
|
||||
endpoint_client::POST<endpoint_client::JSONEmptyBody>,
|
||||
endpoint_client::Response<endpoint_client::JSONEmptyBody,
|
||||
endpoint_client::JSONEmptyBody>>;
|
||||
|
||||
} // namespace brave_account::endpoints
|
||||
|
||||
|
||||
@@ -14,16 +14,6 @@
|
||||
|
||||
namespace brave_account::endpoints {
|
||||
|
||||
bool operator==(const AuthLogout::Response::SuccessBody&,
|
||||
const AuthLogout::Response::SuccessBody&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator==(const AuthLogout::Response::ErrorBody&,
|
||||
const AuthLogout::Response::ErrorBody&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using AuthLogoutTestCase = EndpointTestCase<AuthLogout>;
|
||||
@@ -33,9 +23,10 @@ const AuthLogoutTestCase* Success() {
|
||||
{.test_name = "success",
|
||||
.http_status_code = net::HTTP_NO_CONTENT,
|
||||
.raw_response_body = "",
|
||||
.expected_response = {.net_error = net::OK,
|
||||
.status_code = net::HTTP_NO_CONTENT,
|
||||
.body = AuthLogout::Response::SuccessBody()}});
|
||||
.expected_response = {
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_NO_CONTENT,
|
||||
.body = base::ok(AuthLogout::Response::SuccessBody())}});
|
||||
|
||||
return kSuccess.get();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINTS_AUTH_VALIDATE_H_
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/brave_endpoint.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_empty_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "brave/components/brave_account/endpoints/auth_validate_bodies.h"
|
||||
@@ -17,7 +18,7 @@ namespace brave_account::endpoints {
|
||||
using AuthValidate = endpoint_client::BraveEndpoint<
|
||||
"accounts.bsg",
|
||||
"/v2/auth/validate",
|
||||
endpoint_client::GET<AuthValidateRequestBody>,
|
||||
endpoint_client::GET<endpoint_client::JSONEmptyBody>,
|
||||
endpoint_client::Response<AuthValidateSuccessBody, ErrorBody>>;
|
||||
|
||||
} // namespace brave_account::endpoints
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Schema for the /v2/auth/validate request and response.
|
||||
// Schema for the /v2/auth/validate response.
|
||||
namespace endpoints {
|
||||
dictionary AuthValidateRequestBody {};
|
||||
|
||||
dictionary AuthValidateSuccessBody {
|
||||
DOMString email;
|
||||
};
|
||||
|
||||
@@ -26,19 +26,26 @@
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
namespace brave_account::endpoint_client {
|
||||
|
||||
template <typename T, typename E>
|
||||
inline bool operator==(const Response<T, E>& lhs, const Response<T, E>& rhs) {
|
||||
return std::tie(lhs.net_error, lhs.status_code, lhs.body) ==
|
||||
std::tie(rhs.net_error, rhs.status_code, rhs.body);
|
||||
}
|
||||
|
||||
inline bool operator==(const JSONEmptyBody&, const JSONEmptyBody&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace brave_account::endpoint_client
|
||||
|
||||
namespace brave_account::endpoints {
|
||||
|
||||
inline bool operator==(const ErrorBody& lhs, const ErrorBody& rhs) {
|
||||
return lhs.code == rhs.code;
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
bool operator==(const endpoint_client::Response<T, E>& lhs,
|
||||
const endpoint_client::Response<T, E>& rhs) {
|
||||
return lhs.net_error == rhs.net_error && lhs.status_code == rhs.status_code &&
|
||||
lhs.body == rhs.body;
|
||||
}
|
||||
|
||||
template <endpoint_client::IsEndpoint T>
|
||||
struct EndpointTestCase {
|
||||
std::string test_name;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINTS_PASSWORD_FINALIZE_H_
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/brave_endpoint.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_empty_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "brave/components/brave_account/endpoints/error_body.h"
|
||||
@@ -18,7 +19,7 @@ using PasswordFinalize = endpoint_client::BraveEndpoint<
|
||||
"accounts.bsg",
|
||||
"/v2/accounts/password/finalize",
|
||||
endpoint_client::POST<PasswordFinalizeRequestBody>,
|
||||
endpoint_client::Response<PasswordFinalizeSuccessBody, ErrorBody>>;
|
||||
endpoint_client::Response<endpoint_client::JSONEmptyBody, ErrorBody>>;
|
||||
|
||||
} // namespace brave_account::endpoints
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Schema for the /v2/accounts/password/finalize request and response.
|
||||
// Schema for the /v2/accounts/password/finalize request.
|
||||
namespace endpoints {
|
||||
dictionary PasswordFinalizeRequestBody {
|
||||
DOMString serializedRecord;
|
||||
};
|
||||
|
||||
dictionary PasswordFinalizeSuccessBody {};
|
||||
};
|
||||
|
||||
@@ -15,11 +15,6 @@
|
||||
|
||||
namespace brave_account::endpoints {
|
||||
|
||||
bool operator==(const PasswordFinalize::Response::SuccessBody& lhs,
|
||||
const PasswordFinalize::Response::SuccessBody& rhs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
using PasswordFinalizeTestCase = EndpointTestCase<PasswordFinalize>;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -7,17 +7,18 @@
|
||||
#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINTS_VERIFY_DELETE_H_
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/brave_endpoint.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_empty_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "brave/components/brave_account/endpoints/verify_delete_bodies.h"
|
||||
|
||||
namespace brave_account::endpoints {
|
||||
|
||||
using VerifyDelete = endpoint_client::BraveEndpoint<
|
||||
"accounts.bsg",
|
||||
"/v2/verify",
|
||||
endpoint_client::DELETE<VerifyDeleteRequestBody>,
|
||||
endpoint_client::Response<VerifyDeleteSuccessBody, VerifyDeleteErrorBody>>;
|
||||
endpoint_client::DELETE<endpoint_client::JSONEmptyBody>,
|
||||
endpoint_client::Response<endpoint_client::JSONEmptyBody,
|
||||
endpoint_client::JSONEmptyBody>>;
|
||||
|
||||
} // namespace brave_account::endpoints
|
||||
|
||||
|
||||
@@ -14,16 +14,6 @@
|
||||
|
||||
namespace brave_account::endpoints {
|
||||
|
||||
bool operator==(const VerifyDelete::Response::SuccessBody&,
|
||||
const VerifyDelete::Response::SuccessBody&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator==(const VerifyDelete::Response::ErrorBody&,
|
||||
const VerifyDelete::Response::ErrorBody&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using VerifyDeleteTestCase = EndpointTestCase<VerifyDelete>;
|
||||
@@ -33,9 +23,10 @@ const VerifyDeleteTestCase* Success() {
|
||||
{.test_name = "success",
|
||||
.http_status_code = net::HTTP_NO_CONTENT,
|
||||
.raw_response_body = "",
|
||||
.expected_response = {.net_error = net::OK,
|
||||
.status_code = net::HTTP_NO_CONTENT,
|
||||
.body = VerifyDelete::Response::SuccessBody()}});
|
||||
.expected_response = {
|
||||
.net_error = net::OK,
|
||||
.status_code = net::HTTP_NO_CONTENT,
|
||||
.body = base::ok(VerifyDelete::Response::SuccessBody())}});
|
||||
|
||||
return kSuccess.get();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINTS_VERIFY_RESEND_H_
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/brave_endpoint.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_empty_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "brave/components/brave_account/endpoints/error_body.h"
|
||||
@@ -18,7 +19,7 @@ using VerifyResend = endpoint_client::BraveEndpoint<
|
||||
"accounts.bsg",
|
||||
"/v2/verify/resend",
|
||||
endpoint_client::POST<VerifyResendRequestBody>,
|
||||
endpoint_client::Response<VerifyResendSuccessBody, ErrorBody>>;
|
||||
endpoint_client::Response<endpoint_client::JSONEmptyBody, ErrorBody>>;
|
||||
|
||||
} // namespace brave_account::endpoints
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Schema for the /v2/verify/resend request and response.
|
||||
// Schema for the /v2/verify/resend request.
|
||||
namespace endpoints {
|
||||
dictionary VerifyResendRequestBody {
|
||||
DOMString locale;
|
||||
};
|
||||
|
||||
dictionary VerifyResendSuccessBody {};
|
||||
};
|
||||
|
||||
@@ -13,11 +13,6 @@
|
||||
|
||||
namespace brave_account::endpoints {
|
||||
|
||||
bool operator==(const VerifyResend::Response::SuccessBody&,
|
||||
const VerifyResend::Response::SuccessBody&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using VerifyResendTestCase = EndpointTestCase<VerifyResend>;
|
||||
|
||||
@@ -11,10 +11,6 @@ namespace email_aliases {
|
||||
// Email Aliases – aliases management JSON types
|
||||
// Base URL: https://aliases.bravesoftware.com/manage
|
||||
|
||||
// Request body for POST /manage (generate alias)
|
||||
dictionary GenerateAliasRequest {
|
||||
};
|
||||
|
||||
// Response of POST /manage (generate alias)
|
||||
// Expected when alias is successfully created.
|
||||
dictionary GenerateAliasResponse {
|
||||
@@ -58,10 +54,6 @@ dictionary AliasListEntry {
|
||||
DOMString status;
|
||||
};
|
||||
|
||||
// Request to GET /manage?status=active (alias list result)
|
||||
dictionary AliasListRequest {
|
||||
};
|
||||
|
||||
// Response to GET /manage?status=active (alias list result)
|
||||
dictionary AliasListResponse {
|
||||
// Array of AliasListEntry objects.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#define BRAVE_COMPONENTS_EMAIL_ALIASES_EMAIL_ALIASES_ENDPOINTS_H_
|
||||
|
||||
#include "brave/components/brave_account/endpoint_client/brave_endpoint.h"
|
||||
#include "brave/components/brave_account/endpoint_client/json_empty_body.h"
|
||||
#include "brave/components/brave_account/endpoint_client/request_types.h"
|
||||
#include "brave/components/brave_account/endpoint_client/response.h"
|
||||
#include "brave/components/email_aliases/email_aliases_api.h"
|
||||
@@ -16,13 +17,15 @@ namespace email_aliases::endpoints {
|
||||
using AliasList = brave_account::endpoint_client::BraveEndpoint<
|
||||
"aliases",
|
||||
"/manage?status=active",
|
||||
brave_account::endpoint_client::GET<AliasListRequest>,
|
||||
brave_account::endpoint_client::GET<
|
||||
brave_account::endpoint_client::JSONEmptyBody>,
|
||||
brave_account::endpoint_client::Response<AliasListResponse, ErrorMessage>>;
|
||||
|
||||
using GenerateAlias = brave_account::endpoint_client::BraveEndpoint<
|
||||
"aliases",
|
||||
"/manage",
|
||||
brave_account::endpoint_client::POST<GenerateAliasRequest>,
|
||||
brave_account::endpoint_client::POST<
|
||||
brave_account::endpoint_client::JSONEmptyBody>,
|
||||
brave_account::endpoint_client::Response<GenerateAliasResponse,
|
||||
ErrorMessage>>;
|
||||
|
||||
|
||||
@@ -268,8 +268,8 @@ void EmailAliasesService::UpdateAliasWithToken(
|
||||
if (update_data->active.has_value()) {
|
||||
auto request = MakeRequest<brave_account::endpoint_client::WithHeaders<
|
||||
endpoints::UpdateAlias::Request>>(token.value()->serviceToken);
|
||||
request.alias = alias_email;
|
||||
request.status = *update_data->active ? "active" : "inactive";
|
||||
request.body.alias = alias_email;
|
||||
request.body.status = *update_data->active ? "active" : "inactive";
|
||||
|
||||
refresh_aliases = false; // will be updated in response.
|
||||
brave_account::endpoint_client::Client<endpoints::UpdateAlias>::Send(
|
||||
@@ -302,7 +302,7 @@ void EmailAliasesService::DeleteAliasWithToken(const std::string& alias_email,
|
||||
if (token.has_value()) {
|
||||
auto request = MakeRequest<brave_account::endpoint_client::WithHeaders<
|
||||
endpoints::DeleteAlias::Request>>(token.value()->serviceToken);
|
||||
request.alias = alias_email;
|
||||
request.body.alias = alias_email;
|
||||
brave_account::endpoint_client::Client<endpoints::DeleteAlias>::Send(
|
||||
url_loader_factory_, std::move(request),
|
||||
base::BindOnce(&EmailAliasesService::OnEditAliasResponse,
|
||||
|
||||
Reference in New Issue
Block a user