@@ -183,6 +183,17 @@ private void generateServerOperationTests(OperationShape operation, OperationInd
183
183
onlyIfProtocolMatches (testCase , () -> generateServerResponseTest (operation , testCase ));
184
184
}
185
185
});
186
+ // 3. Generate test cases for each error on each operation.
187
+ for (StructureShape error : operationIndex .getErrors (operation )) {
188
+ if (!error .hasTag ("client-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
+ }
186
197
}
187
198
}
188
199
@@ -288,8 +299,7 @@ private void generateServerRequestTest(OperationShape operation, HttpRequestTest
288
299
});
289
300
290
301
String getHandlerName = "get" + handlerSymbol .getName ();
291
- writer .addImport (getHandlerName , getHandlerName ,
292
- "./protocols/" + ProtocolGenerator .getSanitizedName (protocolGenerator .getName ()));
302
+ writer .addImport (getHandlerName , null , "./server/" );
293
303
294
304
// Cast the service as any so TS will ignore the fact that the type being passed in is incomplete.
295
305
writer .write ("const handler = $L(testService as $T);" , getHandlerName , serviceSymbol );
@@ -477,8 +487,6 @@ private String registerBodyComparatorStub(String mediaType) {
477
487
public void generateServerResponseTest (OperationShape operation , HttpResponseTestCase testCase ) {
478
488
Symbol serviceSymbol = serverSymbolProvider .toSymbol (service );
479
489
Symbol operationSymbol = serverSymbolProvider .toSymbol (operation );
480
- Symbol handlerSymbol = serviceSymbol .expectProperty ("handler" , Symbol .class );
481
- Symbol serviceOperationsSymbol = serviceSymbol .expectProperty ("operations" , Symbol .class );
482
490
testCase .getDocumentation ().ifPresent (writer ::writeDocs );
483
491
String testName = testCase .getId () + ":ServerResponse" ;
484
492
writer .openBlock ("it($S, async () => {" , "});\n " , testName , () -> {
@@ -497,42 +505,7 @@ public void generateServerResponseTest(OperationShape operation, HttpResponseTes
497
505
}
498
506
});
499
507
});
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 );
536
509
});
537
510
}
538
511
@@ -554,6 +527,92 @@ private void generateResponseTest(OperationShape operation, HttpResponseTestCase
554
527
});
555
528
}
556
529
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
+
541
+ testCase .getDocumentation ().ifPresent (writer ::writeDocs );
542
+ String testName = testCase .getId () + ":ServerErrorResponse" ;
543
+ writer .openBlock ("it($S, async () => {" , "});\n " , testName , () -> {
544
+
545
+ // Generates a Partial implementation of the service type that only includes
546
+ // the specific operation under test. Later we'll have to "cast" this with an "as",
547
+ // but using the partial in the meantime will give us proper type checking on the
548
+ // operation we want.
549
+ writer .openBlock ("class TestService implements Partial<$T> {" , "}" , serviceSymbol , () -> {
550
+ writer .openBlock ("$L(input: any, request: HttpRequest): $T {" , "}" ,
551
+ operationSymbol .getName (), outputType , () -> {
552
+ // Write out an object according to what's defined in the test case.
553
+ writer .writeInline ("const response = " );
554
+ testCase .getParams ().accept (new CommandInputNodeVisitor (error , true ));
555
+
556
+ // Add in the necessary wrapping information to make the error satisfy its interface.
557
+ // TODO: having proper constructors for these errors would be really nice so we don't
558
+ // have to do this.
559
+ writer .openBlock ("const error: $T = {" , "};" , errorSymbol , () -> {
560
+ writer .write ("...response," );
561
+ writer .write ("name: $S," , error .getId ().getName ());
562
+ writer .write ("$$fault: $S," , errorTrait .isClientError () ? "client" : "server" );
563
+ writer .write ("$$metadata: {}," );
564
+ });
565
+ writer .write ("throw error;" );
566
+ });
567
+ });
568
+ writeServerResponseTest (operation , testCase );
569
+ });
570
+ }
571
+
572
+ private void writeServerResponseTest (OperationShape operation , HttpResponseTestCase testCase ) {
573
+ Symbol serviceSymbol = serverSymbolProvider .toSymbol (service );
574
+ Symbol operationSymbol = serverSymbolProvider .toSymbol (operation );
575
+ Symbol handlerSymbol = serviceSymbol .expectProperty ("handler" , Symbol .class );
576
+ Symbol serializerSymbol = operationSymbol .expectProperty ("serializerType" , Symbol .class );
577
+ Symbol serviceOperationsSymbol = serviceSymbol .expectProperty ("operations" , Symbol .class );
578
+ writer .write ("const service: any = new TestService()" );
579
+
580
+ // There's a lot of setup here, including creating our own mux, serializers list, and ultimately
581
+ // our own service handler. This is largely in service of avoiding having to go through the
582
+ // request deserializer
583
+ writer .addImport ("httpbinding" , null , "@aws-smithy/server-common" );
584
+ writer .openBlock ("const testMux = new httpbinding.HttpBindingMux<$S, keyof $T>([" , "]);" ,
585
+ service .getId ().getName (), serviceSymbol , () -> {
586
+ writer .openBlock ("new httpbinding.UriSpec<$S, $S>('POST', [], [], {" , "})," ,
587
+ service .getId ().getName (), operation .getId ().getName (), () -> {
588
+ writer .write ("service: $S," , service .getId ().getName ());
589
+ writer .write ("operation: $S," , operation .getId ().getName ());
590
+ });
591
+ });
592
+
593
+ // Extend the existing serializer and replace the deserialize with a noop so we don't have to
594
+ // worry about trying to construct something that matches.
595
+ writer .openBlock ("class TestSerializer extends $T {" , "}" , serializerSymbol , () -> {
596
+ writer .openBlock ("deserialize = (output: any, context: any): Promise<any> => {" , "};" , () -> {
597
+ writer .write ("return Promise.resolve({});" );
598
+ });
599
+ });
600
+
601
+ // Since we aren't going through the deserializer, we don't have to put much in the fake request.
602
+ // Just enough to get it through our test mux.
603
+ writer .write ("const request = new HttpRequest({method: 'POST', hostname: 'example.com'});" );
604
+
605
+ // Create a new serializer factory that always returns our test serializer.
606
+ writer .addImport ("SmithyException" , "__SmithyException" , "@aws-sdk/smithy-client" );
607
+ writer .addImport ("OperationSerializer" , "__OperationSerializer" , "@aws-smithy/server-common" );
608
+ writer .openBlock ("const serFn: (op: $1T) => __OperationSerializer<$2T, $1T, __SmithyException> = (op) =>"
609
+ + " { return new TestSerializer(); };" , serviceOperationsSymbol , serviceSymbol );
610
+
611
+ writer .write ("const handler = new $T(service, testMux, serFn);" , handlerSymbol );
612
+ writer .write ("let r = await handler.handle(request)" ).write ("" );
613
+ writeHttpResponseAssertions (testCase );
614
+ }
615
+
557
616
private void generateErrorResponseTest (
558
617
OperationShape operation ,
559
618
StructureShape error ,
0 commit comments