Skip to content

Commit 3c34bc5

Browse files
Generate ssdk error protocol tests
1 parent 4c93f11 commit 3c34bc5

File tree

2 files changed

+95
-52
lines changed

2 files changed

+95
-52
lines changed

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/HttpProtocolTestGenerator.java

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,17 @@ private void generateServerOperationTests(OperationShape operation, OperationInd
183183
onlyIfProtocolMatches(testCase, () -> generateServerResponseTest(operation, testCase));
184184
}
185185
});
186+
// 3. Generate test cases for each error on each operation.
187+
for (StructureShape error : operationIndex.getErrors(operation)) {
188+
if (!error.hasTag("server-only")) {
189+
error.getTrait(HttpResponseTestsTrait.class).ifPresent(trait -> {
190+
for (HttpResponseTestCase testCase : trait.getTestCasesFor(AppliesTo.SERVER)) {
191+
onlyIfProtocolMatches(testCase,
192+
() -> generateServerErrorResponseTest(operation, error, testCase));
193+
}
194+
});
195+
}
196+
}
186197
}
187198
}
188199

@@ -288,8 +299,7 @@ private void generateServerRequestTest(OperationShape operation, HttpRequestTest
288299
});
289300

290301
String getHandlerName = "get" + handlerSymbol.getName();
291-
writer.addImport(getHandlerName, getHandlerName,
292-
"./protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()));
302+
writer.addImport(getHandlerName, null, "./server/");
293303

294304
// Cast the service as any so TS will ignore the fact that the type being passed in is incomplete.
295305
writer.write("const handler = $L(testService as $T);", getHandlerName, serviceSymbol);
@@ -477,8 +487,6 @@ private String registerBodyComparatorStub(String mediaType) {
477487
public void generateServerResponseTest(OperationShape operation, HttpResponseTestCase testCase) {
478488
Symbol serviceSymbol = serverSymbolProvider.toSymbol(service);
479489
Symbol operationSymbol = serverSymbolProvider.toSymbol(operation);
480-
Symbol handlerSymbol = serviceSymbol.expectProperty("handler", Symbol.class);
481-
Symbol serviceOperationsSymbol = serviceSymbol.expectProperty("operations", Symbol.class);
482490
testCase.getDocumentation().ifPresent(writer::writeDocs);
483491
String testName = testCase.getId() + ":ServerResponse";
484492
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {
@@ -497,42 +505,7 @@ public void generateServerResponseTest(OperationShape operation, HttpResponseTes
497505
}
498506
});
499507
});
500-
501-
writer.write("const service: any = new TestService()");
502-
503-
// There's a lot of setup here, including creating our own mux, serializers list, and ultimately
504-
// our own service handler. This is largely in service of avoiding having to go through the
505-
// request deserializer
506-
writer.addImport("httpbinding", null, "@aws-smithy/server-common");
507-
writer.openBlock("const testMux = new httpbinding.HttpBindingMux<$S, keyof $T>([", "]);",
508-
service.getId().getName(), serviceSymbol, () -> {
509-
writer.openBlock("new httpbinding.UriSpec<$S, $S>('POST', [], [], {", "}),",
510-
service.getId().getName(), operation.getId().getName(), () -> {
511-
writer.write("service: $S,", service.getId().getName());
512-
writer.write("operation: $S,", operation.getId().getName());
513-
});
514-
});
515-
516-
writer.write("const request = new HttpRequest({method: 'POST', hostname: 'example.com'});");
517-
518-
String serializerName = ProtocolGenerator.getGenericSerFunctionName(operationSymbol) + "Response";
519-
writer.addImport(serializerName, serializerName,
520-
"./protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()));
521-
522-
writer.addImport("OperationSerializer", "__OperationSerializer", "@aws-smithy/server-common");
523-
writer.openBlock("const serFn: (op: $1T) => __OperationSerializer<$2T, $1T> = (op) => {", "};",
524-
serviceOperationsSymbol, serviceSymbol, () -> {
525-
writer.openBlock("return {", "};", () -> {
526-
writer.write("serialize: $L,", serializerName);
527-
writer.openBlock("deserialize: (output: any, context: any): Promise<any> => {", "},", () -> {
528-
writer.write("return Promise.resolve({});");
529-
});
530-
});
531-
});
532-
533-
writer.write("const handler = new $T(service, testMux, serFn);", handlerSymbol);
534-
writer.write("let r = await handler.handle(request)").write("");
535-
writeHttpResponseAssertions(testCase);
508+
writeServerResponseTest(operation, testCase);
536509
});
537510
}
538511

@@ -554,6 +527,76 @@ private void generateResponseTest(OperationShape operation, HttpResponseTestCase
554527
});
555528
}
556529

530+
private void generateServerErrorResponseTest(
531+
OperationShape operation,
532+
StructureShape error,
533+
HttpResponseTestCase testCase
534+
) {
535+
Symbol serviceSymbol = serverSymbolProvider.toSymbol(service);
536+
Symbol operationSymbol = serverSymbolProvider.toSymbol(operation);
537+
Symbol outputType = operationSymbol.expectProperty("outputType", Symbol.class);
538+
Symbol errorSymbol = serverSymbolProvider.toSymbol(error);
539+
ErrorTrait errorTrait = error.expectTrait(ErrorTrait.class);
540+
testCase.getDocumentation().ifPresent(writer::writeDocs);
541+
String testName = testCase.getId() + ":ServerErrorResponse";
542+
writer.openBlock("it($S, async () => {", "});\n", testName, () -> {
543+
writer.openBlock("class TestService implements Partial<$T> {", "}", serviceSymbol, () -> {
544+
writer.openBlock("$L(input: any, request: HttpRequest): $T {", "}",
545+
operationSymbol.getName(), outputType, () -> {
546+
writer.writeInline("const response = ");
547+
testCase.getParams().accept(new CommandInputNodeVisitor(error, true));
548+
writer.openBlock("const error: $T = {", "};", errorSymbol, () -> {
549+
writer.write("...response,");
550+
writer.write("name: $S,", error.getId().getName());
551+
writer.write("$$fault: $S,", errorTrait.isClientError() ? "client" : "server");
552+
writer.write("$$metadata: {},");
553+
});
554+
writer.write("throw error;");
555+
});
556+
});
557+
writeServerResponseTest(operation, testCase);
558+
});
559+
}
560+
561+
private void writeServerResponseTest(OperationShape operation, HttpResponseTestCase testCase) {
562+
Symbol serviceSymbol = serverSymbolProvider.toSymbol(service);
563+
Symbol operationSymbol = serverSymbolProvider.toSymbol(operation);
564+
Symbol handlerSymbol = serviceSymbol.expectProperty("handler", Symbol.class);
565+
Symbol serializerSymbol = operationSymbol.expectProperty("serializerType", Symbol.class);
566+
Symbol serviceOperationsSymbol = serviceSymbol.expectProperty("operations", Symbol.class);
567+
writer.write("const service: any = new TestService()");
568+
569+
// There's a lot of setup here, including creating our own mux, serializers list, and ultimately
570+
// our own service handler. This is largely in service of avoiding having to go through the
571+
// request deserializer
572+
writer.addImport("httpbinding", null, "@aws-smithy/server-common");
573+
writer.openBlock("const testMux = new httpbinding.HttpBindingMux<$S, keyof $T>([", "]);",
574+
service.getId().getName(), serviceSymbol, () -> {
575+
writer.openBlock("new httpbinding.UriSpec<$S, $S>('POST', [], [], {", "}),",
576+
service.getId().getName(), operation.getId().getName(), () -> {
577+
writer.write("service: $S,", service.getId().getName());
578+
writer.write("operation: $S,", operation.getId().getName());
579+
});
580+
});
581+
582+
writer.write("const request = new HttpRequest({method: 'POST', hostname: 'example.com'});");
583+
584+
writer.openBlock("class TestSerializer extends $T {", "}", serializerSymbol, () -> {
585+
writer.openBlock("deserialize = (output: any, context: any): Promise<any> => {", "};", () -> {
586+
writer.write("return Promise.resolve({});");
587+
});
588+
});
589+
590+
writer.addImport("SmithyException", "__SmithyException", "@aws-sdk/smithy-client");
591+
writer.addImport("OperationSerializer", "__OperationSerializer", "@aws-smithy/server-common");
592+
writer.openBlock("const serFn: (op: $1T) => __OperationSerializer<$2T, $1T, __SmithyException> = (op) =>"
593+
+ " { return new TestSerializer(); };", serviceOperationsSymbol, serviceSymbol);
594+
595+
writer.write("const handler = new $T(service, testMux, serFn);", handlerSymbol);
596+
writer.write("let r = await handler.handle(request)").write("");
597+
writeHttpResponseAssertions(testCase);
598+
}
599+
557600
private void generateErrorResponseTest(
558601
OperationShape operation,
559602
StructureShape error,

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServerCommandGenerator.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,18 @@ private void writeErrorChecker() {
182182
private void writeErrorHandler() {
183183
writer.addImport("SerdeContext", null, "@aws-sdk/types");
184184
writer.openBlock("serializeError(error: $T, ctx: Omit<SerdeContext, 'endpoint'>): Promise<$T> {", "}",
185-
errorsType, applicationProtocol.getResponseType(), () -> {
186-
if (operation.getErrors().isEmpty()) {
187-
writer.write("throw error;");
188-
} else {
189-
writer.openBlock("switch (error.name) {", "}", () -> {
190-
for (ShapeId errorId : operation.getErrors()) {
191-
writeErrorHandlerCase(errorId);
192-
}
193-
writer.openBlock("default: {", "}", () -> writer.write("throw error;"));
194-
});
195-
}
196-
});
185+
errorsType, applicationProtocol.getResponseType(), () -> {
186+
if (operation.getErrors().isEmpty()) {
187+
writer.write("throw error;");
188+
} else {
189+
writer.openBlock("switch (error.name) {", "}", () -> {
190+
for (ShapeId errorId : operation.getErrors()) {
191+
writeErrorHandlerCase(errorId);
192+
}
193+
writer.openBlock("default: {", "}", () -> writer.write("throw error;"));
194+
});
195+
}
196+
});
197197
writer.write("");
198198
}
199199

0 commit comments

Comments
 (0)