Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cts): add e2e for abtesting #2673

Merged
merged 11 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ private static HttpUrl createHttpUrl(@Nonnull HttpRequest request, RequestOption
.scheme("https")
.host("algolia.com") // will be overridden by the retry strategy
.encodedPath(request.getPath());
request.getQueryParameters().forEach(urlBuilder::addQueryParameter);
request.getQueryParameters().forEach(urlBuilder::addEncodedQueryParameter);
if (requestOptions != null) {
requestOptions.getQueryParameters().forEach(urlBuilder::addQueryParameter);
requestOptions.getQueryParameters().forEach(urlBuilder::addEncodedQueryParameter);
}
return urlBuilder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.algolia.codegen.exceptions.*;
import com.algolia.codegen.utils.*;
import com.algolia.codegen.utils.OneOf;
import com.google.common.collect.Iterables;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.servers.Server;
Expand Down Expand Up @@ -72,6 +73,30 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
return Helpers.specifyCustomRequest(super.fromOperation(path, httpMethod, operation, servers));
}

@Override
public ModelsMap postProcessModels(ModelsMap objs) {
objs = super.postProcessModels(objs);

for (ModelMap modelMap : objs.getModels()) {
CodegenModel model = modelMap.getModel();
if (model.isEnum) {
continue;
}

for (CodegenProperty param : Iterables.concat(model.vars, model.allVars, model.requiredVars, model.optionalVars)) {
if (
!param.isNullable || !param.isPrimitiveType || param.isContainer || param.isFreeFormObject || (param.isAnyType && !param.isModel)
) {
continue;
}

param.dataType = "utils." + param.dataType;
}
}

return objs;
}

@Override
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
Map<String, ModelsMap> models = super.postProcessAllModels(objs);
Expand All @@ -96,8 +121,6 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
prop.dataType = prop.dataType.replace("[]*[]", "[][]");
prop.vendorExtensions.put("x-go-base-type", prop.dataType);
}

prop.dataType = prop.dataType.replace("NullableBool", "utils.NullableBool");
}
}
return models;
Expand Down
5 changes: 5 additions & 0 deletions specs/abtesting/common/schemas/ABTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,28 @@ ABTest:
clickSignificance:
type: number
format: double
nullable: true
description: >
[A/B test significance](https://www.algolia.com/doc/guides/ab-testing/what-is-ab-testing/in-depth/how-ab-test-scores-are-calculated/#statistical-significance-or-chance) based on click data. A value of 0.95 or over is considered to be _significant_.
example: 1
conversionSignificance:
type: number
format: double
nullable: true
description: >
[A/B test significance](https://www.algolia.com/doc/guides/ab-testing/what-is-ab-testing/in-depth/how-ab-test-scores-are-calculated/#statistical-significance-or-chance) based on conversion. A value of 0.95 or over is considered to be _significant_.
example: 1
addToCartSignificance:
type: number
format: double
nullable: true
description: >
[A/B test significance](https://www.algolia.com/doc/guides/ab-testing/what-is-ab-testing/in-depth/how-ab-test-scores-are-calculated/#statistical-significance-or-chance) based on add-to-cart data. A value of 0.95 or over is considered to be _significant_.
example: 1
purchaseSignificance:
type: number
format: double
nullable: true
description: >
[A/B test significance](https://www.algolia.com/doc/guides/ab-testing/what-is-ab-testing/in-depth/how-ab-test-scores-are-calculated/#statistical-significance-or-chance) based on purchase data. A value of 0.95 or over is considered to be _significant_.
example: 1
Expand All @@ -39,6 +43,7 @@ ABTest:
additionalProperties:
type: number
format: double
nullable: true
description: >
[A/B test significance](https://www.algolia.com/doc/guides/ab-testing/what-is-ab-testing/in-depth/how-ab-test-scores-are-calculated/#statistical-significance-or-chance) based on revenue data. A value of 0.95 or over is considered to be _significant_.
example:
Expand Down
14 changes: 13 additions & 1 deletion specs/abtesting/common/schemas/Variant.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ variant:
addToCartRate:
type: number
format: double
nullable: true
description: Variant's [add-to-cart rate](https://www.algolia.com/doc/guides/search-analytics/concepts/metrics/#add-to-cart-rate).
example: 0.0
averageClickPosition:
type: integer
nullable: true
description: Variant's [average click position](https://www.algolia.com/doc/guides/search-analytics/concepts/metrics/#click-position).
example: 0
clickCount:
Expand All @@ -28,6 +30,7 @@ variant:
clickThroughRate:
type: number
format: double
nullable: true
description: Variant's [click-through rate](https://www.algolia.com/doc/guides/search-analytics/concepts/metrics/#click-through-rate).
example: 0.22219857724813036
conversionCount:
Expand All @@ -37,18 +40,24 @@ variant:
conversionRate:
type: number
format: double
nullable: true
description: Variant's [conversion rate](https://www.algolia.com/doc/guides/search-analytics/concepts/metrics/#conversion-rate).
example: 0.14546725846658964
currencies:
$ref: '../parameters.yml#/currencies'
description:
$ref: '../parameters.yml#/description'
estimatedSampleSize:
type: integer
description: The estimated number of searches that will need to be run to achieve the desired confidence level and statistical power. A `minimumDetectableEffect` must be set in the `configuration` object for this to be used.
example: 0
filterEffects:
$ref: '../parameters.yml#/filterEffects'
index:
$ref: '../parameters.yml#/index'
noResultCount:
type: integer
nullable: true
description: Number of [searches without results](https://www.algolia.com/doc/guides/search-analytics/concepts/metrics/#searches-without-results) for that variant.
example: 0
purchaseCount:
Expand All @@ -58,10 +67,12 @@ variant:
purchaseRate:
type: number
format: double
nullable: true
description: Variant's [purchase rate](https://www.algolia.com/doc/guides/search-analytics/concepts/metrics/#purchase-rate).
example: 0.0
searchCount:
type: integer
nullable: true
description: Number of searches carried out during the A/B test.
example: 86269
trackedSearchCount:
Expand All @@ -70,10 +81,12 @@ variant:
$ref: '../parameters.yml#/trafficPercentage'
userCount:
type: integer
nullable: true
description: Number of users during the A/B test.
example: 55501
trackedUserCount:
type: integer
nullable: true
description: Number of users that performed a tracked search during the A/B test.
example: 55501
required:
Expand All @@ -90,7 +103,6 @@ variant:
- clickThroughRate
- clickCount
- averageClickPosition
- currencies
- addToCartRate
- addToCartCount
- purchaseRate
Expand Down
1 change: 1 addition & 0 deletions specs/common/parameters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ page:
trackedSearchCount:
type: integer
example: 2
nullable: true
description: Number of tracked searches. This is the number of search requests where the `clickAnalytics` parameter is `true`.

count:
Expand Down
2 changes: 1 addition & 1 deletion templates/csharp/tests/requests/requests.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private readonly {{client}} _client{{#hasE2E}}, _e2eClient{{/hasE2E}};
throw new Exception("please provide an `ALGOLIA_ADMIN_KEY` env var for e2e tests");
}

_e2eClient = new SearchClient(new SearchConfig(e2EAppId, e2EApiKey));
_e2eClient = new {{client}}(new {{clientPrefix}}Config(e2EAppId, e2EApiKey{{#hasRegionalHost}},"{{defaultRegion}}"{{/hasRegionalHost}}));
{{/hasE2E}}
}

Expand Down
16 changes: 7 additions & 9 deletions templates/go/client.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,14 @@ func (c *APIClient) prepareRequest(
return nil, fmt.Errorf("failed to parse the path: %w", err)
}

// Adding Query Param
query := url.Query()
for k, v := range queryParams {
for _, iv := range v {
query.Add(k, iv)
}
}
var queryString []string
for k, v := range queryParams {
for _, value := range v {
queryString = append(queryString, k + "=" + value)
}
}

// Encode the parameters.
url.RawQuery = query.Encode()
url.RawQuery = strings.Join(queryString, "&")

// Generate a new request

Expand Down
4 changes: 2 additions & 2 deletions templates/python/tests/requests/requests.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ class Test{{#lambda.pascalcase}}{{{client}}}{{/lambda.pascalcase}}{{#isE2E}}E2E{
{{/request}}

{{#response}}
raw_resp = await SearchClient(self._e2e_app_id, self._e2e_api_key).{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}_with_http_info({{#parametersWithDataType}}{{> tests/requests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}} request_options={ {{#requestOptions.headers.parameters}}"headers":loads("""{{{.}}}"""),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}"query_parameters":loads("""{{{.}}}"""),{{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}})
raw_resp = await {{#lambda.pascalcase}}{{{client}}}{{/lambda.pascalcase}}(self._e2e_app_id, self._e2e_api_key{{#hasRegionalHost}}, "{{{defaultRegion}}}"{{/hasRegionalHost}}).{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}_with_http_info({{#parametersWithDataType}}{{> tests/requests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}} request_options={ {{#requestOptions.headers.parameters}}"headers":loads("""{{{.}}}"""),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}"query_parameters":loads("""{{{.}}}"""),{{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}})
{{#statusCode}}
assert raw_resp.status_code == {{statusCode}}
{{/statusCode}}

{{#body}}
resp = await SearchClient(self._e2e_app_id, self._e2e_api_key).{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}({{#parametersWithDataType}}{{> tests/requests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}} request_options={ {{#requestOptions.headers.parameters}}"headers":loads("""{{{.}}}"""),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}"query_parameters":loads("""{{{.}}}"""),{{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}})
resp = await {{#lambda.pascalcase}}{{{client}}}{{/lambda.pascalcase}}(self._e2e_app_id, self._e2e_api_key{{#hasRegionalHost}}, "{{{defaultRegion}}}"{{/hasRegionalHost}}).{{#lambda.snakecase}}{{method}}{{/lambda.snakecase}}({{#parametersWithDataType}}{{> tests/requests/generateParams}}{{/parametersWithDataType}}{{#hasRequestOptions}} request_options={ {{#requestOptions.headers.parameters}}"headers":loads("""{{{.}}}"""),{{/requestOptions.headers.parameters}}{{#requestOptions.queryParameters.parameters}}"query_parameters":loads("""{{{.}}}"""),{{/requestOptions.queryParameters.parameters}} }{{/hasRequestOptions}})
_expected_body = loads("""{{{.}}}""")
assert self._helpers.union(_expected_body, loads(resp.to_json())) == _expected_body
{{/body}}
Expand Down
6 changes: 3 additions & 3 deletions templates/ruby/tests/requests/requests.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class Test{{#lambda.pascalcase}}{{{client}}}{{/lambda.pascalcase}} < Test::Unit:
{{#hasE2E}}
@e2e_client = Algolia::{{#lambda.pascalcase}}{{{client}}}{{/lambda.pascalcase}}.create(
ENV.fetch('ALGOLIA_APPLICATION_ID', nil),
ENV.fetch('ALGOLIA_ADMIN_KEY', nil)
{{#hasRegionalHost}}, '{{{defaultRegion}}}'{{/hasRegionalHost}}
ENV.fetch('ALGOLIA_ADMIN_KEY', nil){{#hasRegionalHost}},
'{{{defaultRegion}}}'{{/hasRegionalHost}}
)
{{/hasE2E}}
end
Expand Down Expand Up @@ -59,4 +59,4 @@ class Test{{#lambda.pascalcase}}{{{client}}}{{/lambda.pascalcase}} < Test::Unit:

{{/tests}}
{{/blocksRequests}}
end
end
48 changes: 42 additions & 6 deletions tests/CTS/requests/abtesting/listABTests.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,55 @@
{
"testName": "listABTests with parameters",
"parameters": {
"offset": 42,
"offset": 0,
"limit": 21,
"indexPrefix": "foo",
"indexSuffix": "bar"
"indexPrefix": "cts_e2e ab",
"indexSuffix": "t"
},
"request": {
"path": "/2/abtests",
"method": "GET",
"queryParameters": {
"offset": "42",
"offset": "0",
"limit": "21",
"indexPrefix": "foo",
"indexSuffix": "bar"
"indexPrefix": "cts_e2e%20ab",
"indexSuffix": "t"
}
},
"response": {
"statusCode": 200,
"body": {
"abtests": [
{
"abTestID": 84617,
"createdAt": "2024-02-06T10:04:30.209477Z",
"endAt": "2024-05-06T09:04:26.469Z",
"name": "cts_e2e_abtest",
"status": "active",
"variants": [
{
"addToCartCount": 0,
"clickCount": 0,
"conversionCount": 0,
"description": "",
"index": "cts_e2e_search_facet",
"purchaseCount": 0,
"trafficPercentage": 25
},
{
"addToCartCount": 0,
"clickCount": 0,
"conversionCount": 0,
"description": "",
"index": "cts_e2e abtest",
"purchaseCount": 0,
"trafficPercentage": 75
}
]
}
],
"count": 1,
"total": 1
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion tests/output/go/tests/echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"net/http"
"net/url"
"strings"
"time"
)

Expand All @@ -24,7 +25,6 @@ func (e *EchoRequester) Request(req *http.Request, timeout time.Duration, connec
e.Path = req.URL.EscapedPath()
e.Method = req.Method
e.Header = req.Header
e.Query = req.URL.Query()
e.Timeout = timeout
e.ConnectTimeout = connectTimeout
if req.Body != nil {
Expand All @@ -35,6 +35,15 @@ func (e *EchoRequester) Request(req *http.Request, timeout time.Duration, connec
e.Body = nil
}

var queryValues = strings.Split(req.URL.RawQuery, "&")
e.Query = url.Values{}
for _, queryValue := range queryValues {
split := strings.Split(queryValue, "=")
if len(split) == 2 {
e.Query.Add(split[0], split[1])
}
}

return &http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString("")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,13 @@ private String processResponseBody(Request request) {

private Map<String, Object> buildQueryParameters(Request request) {
Map<String, Object> params = new HashMap<>();
HttpUrl url = request.url();
for (String name : url.queryParameterNames()) {
for (String value : url.queryParameterValues(name)) {
params.put(name, value);
String query = request.url().encodedQuery();
if (query != null) {
for (String param : query.split("&")) {
String[] pair = param.split("=");
String key = pair[0];
String value = pair.length > 1 ? pair[1] : null;
params.put(key, value);
}
}
return params;
Expand Down
Loading
Loading