@voxgig/sdkgen 4.24.0 → 4.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Deploy.js +9 -6
  3. package/dist/cmp/Deploy.js.map +1 -1
  4. package/dist/helpers/semver.js +3 -1
  5. package/dist/helpers/semver.js.map +1 -1
  6. package/dist/tsconfig.tsbuildinfo +1 -1
  7. package/package.json +4 -4
  8. package/project/.sdk/tm/go/test/pipeline_test.go +163 -57
  9. package/project/.sdk/tm/go/test/primary_utility_test.go +53 -0
  10. package/project/.sdk/tm/java/test/PipelineTest.java +141 -48
  11. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +67 -6
  12. package/project/.sdk/tm/js/test/pipeline.test.js +88 -22
  13. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +47 -1
  14. package/project/.sdk/tm/lua/test/omni.lua +8 -1
  15. package/project/.sdk/tm/lua/test/pipeline_test.lua +107 -28
  16. package/project/.sdk/tm/lua/test/primary_utility_test.lua +83 -3
  17. package/project/.sdk/tm/perl/t/omni.pm +7 -1
  18. package/project/.sdk/tm/perl/t/pipeline.t +124 -27
  19. package/project/.sdk/tm/perl/t/primary_utility.t +73 -0
  20. package/project/.sdk/tm/php/test/PipelineTest.php +143 -26
  21. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +80 -2
  22. package/project/.sdk/tm/py/test/test_pipeline.py +108 -31
  23. package/project/.sdk/tm/py/test/test_primary_utility.py +76 -1
  24. package/project/.sdk/tm/rb/test/pipeline_test.rb +100 -24
  25. package/project/.sdk/tm/rb/test/primary_utility_test.rb +59 -1
  26. package/project/.sdk/tm/ts/test/pipeline.test.ts +52 -11
  27. package/project/sdkgen-package.json +1 -1
  28. package/src/cmp/Deploy.ts +11 -6
  29. package/src/helpers/semver.ts +3 -1
@@ -2,6 +2,7 @@ package sdktest
2
2
 
3
3
 
4
4
  import (
5
+ "regexp"
5
6
  "testing"
6
7
 
7
8
  sdk "GOMODULE"
@@ -464,13 +465,105 @@ func TestPipelineFeatureOrder(t *testing.T) {
464
465
  })
465
466
  }
466
467
 
468
+ // plAuthCookiePair matches a cookie credential as PrepareAuth writes it:
469
+ // `<scheme>=K` for the probe key, with no scheme prefix and nothing else in
470
+ // the bag.
471
+ var plAuthCookiePair = regexp.MustCompile(`^[^=;]+=K$`)
472
+
473
+ // plAuthCred is where this SDK's PrepareAuth actually puts the credential.
474
+ // The name is the API's own scheme name, not always "authorization", so the
475
+ // cases below read it from a probe run instead of asserting a name. Pair is
476
+ // the `<scheme>=` lead-in of a COOKIE credential, which rides the header bag
477
+ // under the key "cookie" instead of taking a header of its own.
478
+ type plAuthCred struct {
479
+ where string
480
+ name string
481
+ value any
482
+ pair string
483
+ }
484
+
485
+ // plAuthBag picks the container by placement. A cookie credential rides the
486
+ // header bag, because a cookie IS a header.
487
+ func plAuthBag(spec *sdk.Spec, where string) map[string]any {
488
+ if "query" == where {
489
+ return spec.Query
490
+ }
491
+ return spec.Headers
492
+ }
493
+
494
+ // plAuth is the options auth block every case passes. `basic: false` is
495
+ // explicit: an HTTP Basic API's generated config carries `auth.basic: true`,
496
+ // and a client that merges it in takes a branch needing a secret as well.
497
+ // With none supplied that branch writes nothing, which the probe would then
498
+ // read as a public API.
499
+ func plAuth(prefix string) map[string]any {
500
+ return map[string]any{"prefix": prefix, "basic": false}
501
+ }
502
+
503
+ // plAuthCredential runs PrepareAuth once with both containers present and
504
+ // reports which one it wrote to, and under what name. Nil means this SDK
505
+ // places no credential at all - a public API - which is a legitimate shape.
506
+ func plAuthProbe(t *testing.T, sdkopts map[string]any) *plAuthCred {
507
+ t.Helper()
508
+ client, utility := plClient(t, sdkopts)
509
+ ctx := plCtx(client, utility, nil)
510
+ ctx.Spec = sdk.NewSpec(map[string]any{"step": "s"})
511
+ if _, err := utility.PrepareAuth(ctx); err != nil {
512
+ return nil
513
+ }
514
+ for _, where := range []string{"headers", "query"} {
515
+ for name, value := range plAuthBag(ctx.Spec, where) {
516
+ pair := ""
517
+ text, is := value.(string)
518
+ if "headers" == where && "cookie" == name && is &&
519
+ plAuthCookiePair.MatchString(text) {
520
+ pair = text[:len(text)-1]
521
+ }
522
+ return &plAuthCred{where: where, name: name, value: value, pair: pair}
523
+ }
524
+ }
525
+ return nil
526
+ }
527
+
528
+ func plAuthCredential(t *testing.T) *plAuthCred {
529
+ t.Helper()
530
+ return plAuthProbe(t, map[string]any{"apikey": "K", "auth": plAuth("Bearer")})
531
+ }
532
+
533
+ // plAuthAnyCredential is every credential this SDK could possibly place: both
534
+ // credentials and Basic switched on, so whichever branch the API has,
535
+ // something lands unless the API is public.
536
+ func plAuthAnyCredential(t *testing.T) *plAuthCred {
537
+ t.Helper()
538
+ return plAuthProbe(t, map[string]any{
539
+ "apikey": "K",
540
+ "secret": "S",
541
+ "auth": map[string]any{"prefix": "Bearer", "basic": true},
542
+ })
543
+ }
544
+
467
545
  func TestPipelinePrepareAuth(t *testing.T) {
468
546
 
469
- authSpec := func(headers map[string]any) *sdk.Spec {
470
- if headers == nil {
471
- headers = map[string]any{}
547
+ cred := plAuthCredential(t)
548
+
549
+ // placed runs PrepareAuth over a fresh spec, seeding the credential slot
550
+ // first when seed is non-nil, and reports what is left there.
551
+ placed := func(t *testing.T, sdkopts map[string]any, seed any) (any, bool) {
552
+ t.Helper()
553
+ client, utility := plClient(t, sdkopts)
554
+ ctx := plCtx(client, utility, nil)
555
+ ctx.Spec = sdk.NewSpec(map[string]any{"step": "s"})
556
+ if cred != nil && seed != nil {
557
+ plAuthBag(ctx.Spec, cred.where)[cred.name] = seed
558
+ }
559
+ if _, err := utility.PrepareAuth(ctx); err != nil {
560
+ t.Fatalf("unexpected error: %v", err)
472
561
  }
473
- return sdk.NewSpec(map[string]any{"headers": headers, "step": "s"})
562
+ if cred == nil {
563
+ return nil, false
564
+ }
565
+ value, has := plAuthBag(ctx.Spec, cred.where)[cred.name]
566
+ return value, has
474
567
  }
475
568
 
476
569
  t.Run("guards-missing-spec", func(t *testing.T) {
@@ -482,86 +575,99 @@ func TestPipelinePrepareAuth(t *testing.T) {
482
575
  }
483
576
  })
484
577
 
485
- t.Run("apikey-with-prefix-space-joined", func(t *testing.T) {
486
- client, utility := plClient(t, map[string]any{
487
- "apikey": "K",
488
- "auth": map[string]any{"prefix": "Bearer"},
489
- })
490
- ctx := plCtx(client, utility, nil)
491
- ctx.Spec = authSpec(nil)
492
- if _, err := utility.PrepareAuth(ctx); err != nil {
493
- t.Fatalf("unexpected error: %v", err)
578
+ // Without this the cases below cannot fail for an SDK whose credential the
579
+ // probe misses: every one of them takes the public-API path instead.
580
+ t.Run("probe-finds-the-credential-this-sdk-places", func(t *testing.T) {
581
+ if (cred == nil) != (plAuthAnyCredential(t) == nil) {
582
+ t.Error("the probe missed a credential this SDK places, so every " +
583
+ "case below takes the public-API path")
584
+ }
585
+ })
586
+
587
+ t.Run("apikey-placed-where-this-api-puts-it", func(t *testing.T) {
588
+ if cred == nil {
589
+ // A public API places nothing, and that is the whole assertion.
590
+ if _, has := placed(t, map[string]any{
591
+ "apikey": "K",
592
+ "auth": plAuth("Bearer"),
593
+ }, nil); has {
594
+ t.Errorf("expected no credential placed at all")
595
+ }
596
+ return
597
+ }
598
+ if cred.where != "headers" && cred.where != "query" {
599
+ t.Fatalf("unexpected credential container %q", cred.where)
600
+ }
601
+ if "" != cred.pair {
602
+ // A cookie credential is a `<scheme>=<key>` pair, and the scheme
603
+ // name leaves no room for the option's prefix.
604
+ text, _ := cred.value.(string)
605
+ if !plAuthCookiePair.MatchString(text) {
606
+ t.Errorf("expected a cookie pair, got %v", cred.value)
607
+ }
608
+ return
609
+ }
610
+ // A header credential is prefix-joined; a query credential is the raw
611
+ // key, because a query parameter has nowhere to put a scheme name.
612
+ want := "Bearer K"
613
+ if "query" == cred.where {
614
+ want = "K"
494
615
  }
495
- if ctx.Spec.Headers["authorization"] != "Bearer K" {
496
- t.Errorf("expected 'Bearer K', got %v", ctx.Spec.Headers["authorization"])
616
+ if cred.value != want {
617
+ t.Errorf("expected %q in %s[%q], got %v",
618
+ want, cred.where, cred.name, cred.value)
497
619
  }
498
620
  })
499
621
 
500
622
  t.Run("raw-apikey-empty-prefix-as-is", func(t *testing.T) {
501
- client, utility := plClient(t, map[string]any{
623
+ value, has := placed(t, map[string]any{
502
624
  "apikey": "K",
503
- "auth": map[string]any{"prefix": ""},
504
- })
505
- ctx := plCtx(client, utility, nil)
506
- ctx.Spec = authSpec(nil)
507
- if _, err := utility.PrepareAuth(ctx); err != nil {
508
- t.Fatalf("unexpected error: %v", err)
625
+ "auth": plAuth(""),
626
+ }, nil)
627
+ if cred == nil {
628
+ if has {
629
+ t.Errorf("expected no credential placed at all")
630
+ }
631
+ return
509
632
  }
510
- if ctx.Spec.Headers["authorization"] != "K" {
511
- t.Errorf("expected raw 'K', got %v", ctx.Spec.Headers["authorization"])
633
+ if value != cred.pair+"K" {
634
+ t.Errorf("expected %q, got %v", cred.pair+"K", value)
512
635
  }
513
636
  })
514
637
 
515
- t.Run("empty-apikey-drops-header", func(t *testing.T) {
516
- client, utility := plClient(t, map[string]any{
638
+ t.Run("empty-apikey-drops-credential", func(t *testing.T) {
639
+ if _, has := placed(t, map[string]any{
517
640
  "apikey": "",
518
- "auth": map[string]any{"prefix": "Bearer"},
519
- })
520
- ctx := plCtx(client, utility, nil)
521
- ctx.Spec = authSpec(map[string]any{"authorization": "stale"})
522
- if _, err := utility.PrepareAuth(ctx); err != nil {
523
- t.Fatalf("unexpected error: %v", err)
524
- }
525
- if _, has := ctx.Spec.Headers["authorization"]; has {
526
- t.Errorf("expected authorization dropped, got %v", ctx.Spec.Headers["authorization"])
641
+ "auth": plAuth("Bearer"),
642
+ }, "stale"); has {
643
+ t.Errorf("expected the credential dropped")
527
644
  }
528
645
  })
529
646
 
530
- t.Run("missing-apikey-drops-header", func(t *testing.T) {
531
- client, utility := plClient(t, map[string]any{
532
- "auth": map[string]any{"prefix": "Bearer"},
533
- })
647
+ t.Run("missing-apikey-drops-credential", func(t *testing.T) {
648
+ sdkopts := map[string]any{"auth": plAuth("Bearer")}
649
+ client, _ := plClient(t, sdkopts)
534
650
  options := client.OptionsMap()
535
651
  if apikey, _ := options["apikey"].(string); apikey != "" {
536
652
  t.Skip("SDK options carry a configured apikey; case not reproducible here")
537
653
  }
538
- ctx := plCtx(client, utility, nil)
539
- ctx.Spec = authSpec(map[string]any{"authorization": "stale"})
540
- if _, err := utility.PrepareAuth(ctx); err != nil {
541
- t.Fatalf("unexpected error: %v", err)
542
- }
543
- if _, has := ctx.Spec.Headers["authorization"]; has {
544
- t.Errorf("expected authorization dropped, got %v", ctx.Spec.Headers["authorization"])
654
+ if _, has := placed(t, sdkopts, "stale"); has {
655
+ t.Errorf("expected the credential dropped")
545
656
  }
546
657
  })
547
658
 
548
- t.Run("public-api-no-auth-block-drops-header", func(t *testing.T) {
549
- client, utility := plClient(t, map[string]any{"apikey": "K"})
550
- options := client.OptionsMap()
551
- if options["auth"] != nil {
659
+ t.Run("public-api-no-auth-block-drops-credential", func(t *testing.T) {
660
+ sdkopts := map[string]any{"apikey": "K"}
661
+ client, _ := plClient(t, sdkopts)
662
+ if client.OptionsMap()["auth"] != nil {
552
663
  // Option validation supplies an auth shape for this SDK, so a
553
664
  // truly auth-less client cannot be constructed here (the ts test
554
665
  // fakes the client object; Go's prepareAuth reads the concrete
555
666
  // client options).
556
667
  t.Skip("options always carry an auth block in this SDK")
557
668
  }
558
- ctx := plCtx(client, utility, nil)
559
- ctx.Spec = authSpec(map[string]any{"authorization": "stale"})
560
- if _, err := utility.PrepareAuth(ctx); err != nil {
561
- t.Fatalf("unexpected error: %v", err)
562
- }
563
- if _, has := ctx.Spec.Headers["authorization"]; has {
564
- t.Errorf("expected authorization dropped, got %v", ctx.Spec.Headers["authorization"])
669
+ if _, has := placed(t, sdkopts, "stale"); has {
670
+ t.Errorf("expected the credential dropped")
565
671
  }
566
672
  })
567
673
  }
@@ -618,6 +618,23 @@ func TestPrimaryUtility(t *testing.T) {
618
618
  authClient := sdk.TestSDK(nil, setupOpts)
619
619
  authUtility := authClient.GetUtility()
620
620
 
621
+ // The corpus writes the credential as `headers.authorization`: a
622
+ // PLACEHOLDER each runner points at the container and name this API
623
+ // actually uses. plAuthCredential (pipeline_test.go, same package)
624
+ // discovers both by running PrepareAuth once.
625
+ cred := plAuthCredential(t)
626
+ if cred == nil {
627
+ t.Fatal("prepareAuth placed no credential in headers or query")
628
+ }
629
+
630
+ // An absent section is runsection's report to make, not a nil-map write.
631
+ if section, has := primary["prepareAuth"].(map[string]any); has &&
632
+ ("headers" != cred.where || "authorization" != cred.name) {
633
+ original := section["basic"]
634
+ section["basic"] = retargetAuth(original, cred)
635
+ defer func() { section["basic"] = original }()
636
+ }
637
+
621
638
  runsection(t, "prepareAuth", func(args ...any) (any, error) {
622
639
  ctx := omniCtx(args[0], authClient, authUtility)
623
640
 
@@ -823,3 +840,39 @@ func makeTestFullCtx(client *sdk.ProjectNameSDK, utility *sdk.Utility) *sdk.Cont
823
840
 
824
841
  // useVS prevents unused import error
825
842
  var _ = vs.Clone
843
+
844
+
845
+ // retargetAuth rewrites the corpus's `headers` bag to the container this
846
+ // SDK's PrepareAuth writes to, and the `authorization` key inside it to the
847
+ // API's own credential name. Applied only to the prepareAuth section, so
848
+ // real header assertions elsewhere are untouched.
849
+ func retargetAuth(node any, cred *plAuthCred) any {
850
+ switch value := node.(type) {
851
+ case map[string]any:
852
+ out := map[string]any{}
853
+ for key, child := range value {
854
+ if "headers" == key {
855
+ bag := map[string]any{}
856
+ inner, _ := child.(map[string]any)
857
+ for bagkey, bagvalue := range inner {
858
+ name := bagkey
859
+ if "authorization" == bagkey {
860
+ name = cred.name
861
+ }
862
+ bag[name] = retargetAuth(bagvalue, cred)
863
+ }
864
+ out[cred.where] = bag
865
+ } else {
866
+ out[key] = retargetAuth(child, cred)
867
+ }
868
+ }
869
+ return out
870
+ case []any:
871
+ out := make([]any, len(value))
872
+ for index, child := range value {
873
+ out[index] = retargetAuth(child, cred)
874
+ }
875
+ return out
876
+ }
877
+ return node
878
+ }
@@ -11,7 +11,6 @@ package JAVAPACKAGE.sdktest;
11
11
  import static org.junit.jupiter.api.Assertions.assertEquals;
12
12
  import static org.junit.jupiter.api.Assertions.assertFalse;
13
13
  import static org.junit.jupiter.api.Assertions.assertNotNull;
14
- import static org.junit.jupiter.api.Assertions.assertNull;
15
14
  import static org.junit.jupiter.api.Assertions.assertTrue;
16
15
  import static org.junit.jupiter.api.Assertions.fail;
17
16
 
@@ -23,6 +22,7 @@ import java.util.ArrayList;
23
22
  import java.util.LinkedHashMap;
24
23
  import java.util.List;
25
24
  import java.util.Map;
25
+ import java.util.regex.Pattern;
26
26
 
27
27
  import org.junit.jupiter.api.Test;
28
28
 
@@ -529,13 +529,98 @@ public class PipelineTest {
529
529
 
530
530
  // --- prepareAuth --------------------------------------------------------------
531
531
 
532
- static Spec authSpec(Map<String, Object> headers) {
532
+ // A cookie credential as prepareAuth writes it: `<scheme>=K` for the probe
533
+ // key, with no scheme prefix and nothing else in the bag.
534
+ static final Pattern COOKIE_PAIR = Pattern.compile("^[^=;]+=K$");
535
+
536
+ static Spec authSpec() {
533
537
  Map<String, Object> m = new LinkedHashMap<>();
534
- m.put("headers", headers == null ? new LinkedHashMap<>() : headers);
538
+ m.put("headers", new LinkedHashMap<String, Object>());
539
+ m.put("query", new LinkedHashMap<String, Object>());
535
540
  m.put("step", "s");
536
541
  return new Spec(m);
537
542
  }
538
543
 
544
+ // Where this SDK's prepareAuth actually puts the credential. The name is
545
+ // the API's OWN scheme name, not always "authorization", so it is
546
+ // discovered rather than asserted. `pair` is the `<scheme>=` lead-in of a
547
+ // COOKIE credential, which rides the header bag under the key "cookie"
548
+ // instead of taking a header of its own.
549
+ record AuthCred(String where, String name, Object value, String pair) {
550
+ }
551
+
552
+ // A cookie credential rides the header bag, because a cookie IS a header.
553
+ static Map<String, Object> authBag(Spec spec, String where) {
554
+ return "query".equals(where) ? spec.query : spec.headers;
555
+ }
556
+
557
+ // `basic: false` is explicit: an HTTP Basic API's generated config carries
558
+ // `auth.basic: true`, testSDK merges it in, and the branch it selects needs
559
+ // a secret as well. With none supplied that branch deliberately writes
560
+ // nothing, which the probe below would read as a public API.
561
+ static Map<String, Object> authBlock(String prefix) {
562
+ return fhMap("prefix", prefix, "basic", Boolean.FALSE);
563
+ }
564
+
565
+ // Run prepareAuth with both containers present and see which one the
566
+ // generated utility writes to, and under what name. Null means this SDK
567
+ // places no credential at all - a public API - which is a legitimate
568
+ // shape, and the tests below assert exactly that instead.
569
+ static AuthCred authProbe(Map<String, Object> sdkopts) {
570
+ ProjectNameSDK client = plClient(sdkopts);
571
+ Utility utility = client.getUtility();
572
+ Context ctx = plCtx(client, utility, null);
573
+ ctx.spec = authSpec();
574
+ utility.prepareAuth.apply(ctx);
575
+ for (String where : new String[] { "headers", "query" }) {
576
+ for (Map.Entry<String, Object> entry : authBag(ctx.spec, where).entrySet()) {
577
+ Object value = entry.getValue();
578
+ String pair = "";
579
+ if ("headers".equals(where) && "cookie".equals(entry.getKey())
580
+ && value instanceof String
581
+ && COOKIE_PAIR.matcher((String) value).matches()) {
582
+ String text = (String) value;
583
+ pair = text.substring(0, text.length() - 1);
584
+ }
585
+ return new AuthCred(where, entry.getKey(), value, pair);
586
+ }
587
+ }
588
+ return null;
589
+ }
590
+
591
+ static AuthCred authCredential() {
592
+ return authProbe(fhMap("apikey", "K", "auth", authBlock("Bearer")));
593
+ }
594
+
595
+ // Every credential this SDK could possibly place: both credentials and
596
+ // Basic switched on, so whichever branch the API has, something lands
597
+ // unless the API is public.
598
+ static AuthCred authAnyCredential() {
599
+ return authProbe(fhMap(
600
+ "apikey", "K",
601
+ "secret", "S",
602
+ "auth", fhMap("prefix", "Bearer", "basic", Boolean.TRUE)));
603
+ }
604
+
605
+ // Runs prepareAuth over a fresh spec, seeding the credential slot first
606
+ // when seed is non-null, and reports what is left there.
607
+ static Object[] authPlaced(Map<String, Object> sdkopts, Object seed) {
608
+ AuthCred cred = authCredential();
609
+ ProjectNameSDK client = plClient(sdkopts);
610
+ Utility utility = client.getUtility();
611
+ Context ctx = plCtx(client, utility, null);
612
+ ctx.spec = authSpec();
613
+ if (cred != null && seed != null) {
614
+ authBag(ctx.spec, cred.where()).put(cred.name(), seed);
615
+ }
616
+ utility.prepareAuth.apply(ctx);
617
+ if (cred == null) {
618
+ return new Object[] { null, Boolean.FALSE };
619
+ }
620
+ Map<String, Object> bag = authBag(ctx.spec, cred.where());
621
+ return new Object[] { bag.get(cred.name()), bag.containsKey(cred.name()) };
622
+ }
623
+
539
624
  @Test
540
625
  public void prepareAuth_guardsMissingSpec() {
541
626
  ProjectNameSDK client = plClient(fhMap("apikey", "K"));
@@ -545,74 +630,82 @@ public class PipelineTest {
545
630
  assertEquals("auth_no_spec", errCodeOf(() -> utility.prepareAuth.apply(ctx)));
546
631
  }
547
632
 
633
+ // Without this the cases below cannot fail for an SDK whose credential the
634
+ // probe misses: every one of them takes the public-API path instead.
548
635
  @Test
549
- public void prepareAuth_apikeyWithPrefixSpaceJoined() {
550
- ProjectNameSDK client = plClient(fhMap(
551
- "apikey", "K",
552
- "auth", fhMap("prefix", "Bearer")));
553
- Utility utility = client.getUtility();
554
- Context ctx = plCtx(client, utility, null);
555
- ctx.spec = authSpec(null);
556
- utility.prepareAuth.apply(ctx);
557
- assertEquals("Bearer K", ctx.spec.headers.get("authorization"));
636
+ public void prepareAuth_probeFindsTheCredentialThisSdkPlaces() {
637
+ assertEquals(null == authCredential(), null == authAnyCredential(),
638
+ "the probe missed a credential this SDK places, so every case "
639
+ + "takes the public-API path");
640
+ }
641
+
642
+ @Test
643
+ public void prepareAuth_placedWhereThisApiPutsIt() {
644
+ AuthCred cred = authCredential();
645
+ if (cred == null) {
646
+ // A public API places nothing, and that is the whole assertion.
647
+ Object[] got = authPlaced(fhMap(
648
+ "apikey", "K",
649
+ "auth", authBlock("Bearer")), null);
650
+ assertFalse((Boolean) got[1], "expected no credential placed at all");
651
+ return;
652
+ }
653
+ assertTrue("headers".equals(cred.where()) || "query".equals(cred.where()),
654
+ "unexpected credential container " + cred.where());
655
+ if (!"".equals(cred.pair())) {
656
+ // A cookie credential is a `<scheme>=<key>` pair, and the scheme name
657
+ // leaves no room for the option's prefix.
658
+ assertTrue(COOKIE_PAIR.matcher(String.valueOf(cred.value())).matches(),
659
+ "expected a cookie pair, got " + cred.value());
660
+ return;
661
+ }
662
+ // A header credential is prefix-joined; a query credential is the raw
663
+ // key, because a query parameter has nowhere to put a scheme name.
664
+ assertEquals("query".equals(cred.where()) ? "K" : "Bearer K", cred.value());
558
665
  }
559
666
 
560
667
  @Test
561
668
  public void prepareAuth_rawApikeyEmptyPrefixAsIs() {
562
- ProjectNameSDK client = plClient(fhMap(
669
+ Object[] got = authPlaced(fhMap(
563
670
  "apikey", "K",
564
- "auth", fhMap("prefix", "")));
565
- Utility utility = client.getUtility();
566
- Context ctx = plCtx(client, utility, null);
567
- ctx.spec = authSpec(null);
568
- utility.prepareAuth.apply(ctx);
569
- assertEquals("K", ctx.spec.headers.get("authorization"));
671
+ "auth", authBlock("")), null);
672
+ AuthCred cred = authCredential();
673
+ if (cred == null) {
674
+ assertFalse((Boolean) got[1], "expected no credential placed at all");
675
+ return;
676
+ }
677
+ assertEquals(cred.pair() + "K", got[0]);
570
678
  }
571
679
 
572
680
  @Test
573
- public void prepareAuth_emptyApikeyDropsHeader() {
574
- ProjectNameSDK client = plClient(fhMap(
681
+ public void prepareAuth_emptyApikeyDropsCredential() {
682
+ Object[] got = authPlaced(fhMap(
575
683
  "apikey", "",
576
- "auth", fhMap("prefix", "Bearer")));
577
- Utility utility = client.getUtility();
578
- Context ctx = plCtx(client, utility, null);
579
- ctx.spec = authSpec(fhMap("authorization", "stale"));
580
- utility.prepareAuth.apply(ctx);
581
- assertFalse(ctx.spec.headers.containsKey("authorization"),
582
- "expected authorization dropped");
684
+ "auth", authBlock("Bearer")), "stale");
685
+ assertFalse((Boolean) got[1], "expected the credential dropped");
583
686
  }
584
687
 
585
688
  @Test
586
- public void prepareAuth_missingApikeyDropsHeader() {
587
- ProjectNameSDK client = plClient(fhMap(
588
- "auth", fhMap("prefix", "Bearer")));
589
- Map<String, Object> options = client.optionsMap();
590
- Object apikey = options.get("apikey");
689
+ public void prepareAuth_missingApikeyDropsCredential() {
690
+ Map<String, Object> sdkopts = fhMap("auth", authBlock("Bearer"));
691
+ Object apikey = plClient(sdkopts).optionsMap().get("apikey");
591
692
  if (apikey instanceof String && !"".equals(apikey)) {
592
693
  // SDK options carry a configured apikey; case not reproducible here.
593
694
  return;
594
695
  }
595
- Utility utility = client.getUtility();
596
- Context ctx = plCtx(client, utility, null);
597
- ctx.spec = authSpec(fhMap("authorization", "stale"));
598
- utility.prepareAuth.apply(ctx);
599
- assertFalse(ctx.spec.headers.containsKey("authorization"),
600
- "expected authorization dropped");
696
+ Object[] got = authPlaced(sdkopts, "stale");
697
+ assertFalse((Boolean) got[1], "expected the credential dropped");
601
698
  }
602
699
 
603
700
  @Test
604
- public void prepareAuth_publicApiNoAuthBlockDropsHeader() {
605
- ProjectNameSDK client = plClient(fhMap("apikey", "K"));
606
- Map<String, Object> options = client.optionsMap();
607
- if (options.get("auth") != null) {
701
+ public void prepareAuth_publicApiNoAuthBlockDropsCredential() {
702
+ Map<String, Object> sdkopts = fhMap("apikey", "K");
703
+ if (plClient(sdkopts).optionsMap().get("auth") != null) {
608
704
  // Option validation supplies an auth shape for this SDK, so a truly
609
705
  // auth-less client cannot be constructed here.
610
706
  return;
611
707
  }
612
- Utility utility = client.getUtility();
613
- Context ctx = plCtx(client, utility, null);
614
- ctx.spec = authSpec(fhMap("authorization", "stale"));
615
- utility.prepareAuth.apply(ctx);
616
- assertNull(ctx.spec.headers.get("authorization"), "expected authorization dropped");
708
+ Object[] got = authPlaced(sdkopts, "stale");
709
+ assertFalse((Boolean) got[1], "expected the credential dropped");
617
710
  }
618
711
  }
@@ -100,6 +100,44 @@ public class PrimaryUtilityTest {
100
100
  run.runset(basic, subject);
101
101
  }
102
102
 
103
+ // Rename the corpus's `headers` bag to the real container, and the
104
+ // `authorization` key inside it to the real credential name. Applied only
105
+ // to the prepareAuth section, so real header assertions elsewhere are
106
+ // untouched.
107
+ @SuppressWarnings("unchecked")
108
+ static Object retargetAuth(Object node, PipelineTest.AuthCred cred) {
109
+ if (node instanceof List) {
110
+ List<Object> out = new ArrayList<>();
111
+ for (Object child : (List<Object>) node) {
112
+ out.add(retargetAuth(child, cred));
113
+ }
114
+ return out;
115
+ }
116
+ if (!(node instanceof Map)) {
117
+ return node;
118
+ }
119
+
120
+ Map<String, Object> out = new LinkedHashMap<>();
121
+ for (Map.Entry<String, Object> entry : ((Map<String, Object>) node).entrySet()) {
122
+ if ("headers".equals(entry.getKey())) {
123
+ Map<String, Object> bag = new LinkedHashMap<>();
124
+ Map<String, Object> inner = Helpers.toMapAny(entry.getValue());
125
+ if (inner != null) {
126
+ for (Map.Entry<String, Object> bagentry : inner.entrySet()) {
127
+ String name = "authorization".equals(bagentry.getKey())
128
+ ? cred.name() : bagentry.getKey();
129
+ bag.put(name, retargetAuth(bagentry.getValue(), cred));
130
+ }
131
+ }
132
+ out.put(cred.where(), bag);
133
+ }
134
+ else {
135
+ out.put(entry.getKey(), retargetAuth(entry.getValue(), cred));
136
+ }
137
+ }
138
+ return out;
139
+ }
140
+
103
141
  // Helper: create basic test context.
104
142
  static Context makeTestCtx(ProjectNameSDK client, Utility utility,
105
143
  Map<String, Object> overrides) {
@@ -684,15 +722,38 @@ public class PrimaryUtilityTest {
684
722
  ProjectNameSDK authClient = ProjectNameSDK.testSDK(null, setupOpts);
685
723
  Utility authUtility = authClient.getUtility();
686
724
 
687
- runsection("prepareAuth", (args) -> {
688
- Context ctx = OmniResolver.omniCtx(args[0], authClient, authUtility);
725
+ // The corpus writes the credential as `headers.authorization`: a
726
+ // PLACEHOLDER each runner points at the container and name this API
727
+ // actually uses. PipelineTest.authCredential (same package) discovers
728
+ // both by running prepareAuth once.
729
+ PipelineTest.AuthCred cred = PipelineTest.authCredential();
730
+ assertNotNull(cred, "prepareAuth placed no credential in headers or query");
731
+
732
+ // An absent section is runsection's report to make.
733
+ Map<String, Object> section = Helpers.toMapAny(run().spec.get("prepareAuth"));
734
+ boolean swap = section != null
735
+ && (!"headers".equals(cred.where()) || !"authorization".equals(cred.name()));
736
+ Object original = swap ? section.get("basic") : null;
737
+ if (swap) {
738
+ section.put("basic", retargetAuth(original, cred));
739
+ }
689
740
 
690
- authUtility.prepareAuth.apply(ctx);
741
+ try {
742
+ runsection("prepareAuth", (args) -> {
743
+ Context ctx = OmniResolver.omniCtx(args[0], authClient, authUtility);
691
744
 
692
- OmniResolver.omniSyncCtx(args[0], ctx);
745
+ authUtility.prepareAuth.apply(ctx);
693
746
 
694
- return null;
695
- });
747
+ OmniResolver.omniSyncCtx(args[0], ctx);
748
+
749
+ return null;
750
+ });
751
+ }
752
+ finally {
753
+ if (swap) {
754
+ section.put("basic", original);
755
+ }
756
+ }
696
757
  }
697
758
 
698
759
  @Test