@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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/Deploy.js +9 -6
- package/dist/cmp/Deploy.js.map +1 -1
- package/dist/helpers/semver.js +3 -1
- package/dist/helpers/semver.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/project/.sdk/tm/go/test/pipeline_test.go +163 -57
- package/project/.sdk/tm/go/test/primary_utility_test.go +53 -0
- package/project/.sdk/tm/java/test/PipelineTest.java +141 -48
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +67 -6
- package/project/.sdk/tm/js/test/pipeline.test.js +88 -22
- package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +47 -1
- package/project/.sdk/tm/lua/test/omni.lua +8 -1
- package/project/.sdk/tm/lua/test/pipeline_test.lua +107 -28
- package/project/.sdk/tm/lua/test/primary_utility_test.lua +83 -3
- package/project/.sdk/tm/perl/t/omni.pm +7 -1
- package/project/.sdk/tm/perl/t/pipeline.t +124 -27
- package/project/.sdk/tm/perl/t/primary_utility.t +73 -0
- package/project/.sdk/tm/php/test/PipelineTest.php +143 -26
- package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +80 -2
- package/project/.sdk/tm/py/test/test_pipeline.py +108 -31
- package/project/.sdk/tm/py/test/test_primary_utility.py +76 -1
- package/project/.sdk/tm/rb/test/pipeline_test.rb +100 -24
- package/project/.sdk/tm/rb/test/primary_utility_test.rb +59 -1
- package/project/.sdk/tm/ts/test/pipeline.test.ts +52 -11
- package/project/sdkgen-package.json +1 -1
- package/src/cmp/Deploy.ts +11 -6
- 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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
|
496
|
-
t.Errorf("expected
|
|
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
|
-
|
|
623
|
+
value, has := placed(t, map[string]any{
|
|
502
624
|
"apikey": "K",
|
|
503
|
-
"auth":
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
|
511
|
-
t.Errorf("expected
|
|
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-
|
|
516
|
-
|
|
638
|
+
t.Run("empty-apikey-drops-credential", func(t *testing.T) {
|
|
639
|
+
if _, has := placed(t, map[string]any{
|
|
517
640
|
"apikey": "",
|
|
518
|
-
"auth":
|
|
519
|
-
})
|
|
520
|
-
|
|
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-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
539
|
-
|
|
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-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if
|
|
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
|
-
|
|
559
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
|
550
|
-
|
|
551
|
-
"
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
669
|
+
Object[] got = authPlaced(fhMap(
|
|
563
670
|
"apikey", "K",
|
|
564
|
-
"auth",
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|
|
574
|
-
|
|
681
|
+
public void prepareAuth_emptyApikeyDropsCredential() {
|
|
682
|
+
Object[] got = authPlaced(fhMap(
|
|
575
683
|
"apikey", "",
|
|
576
|
-
"auth",
|
|
577
|
-
|
|
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
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
596
|
-
|
|
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
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
613
|
-
|
|
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
|
-
|
|
688
|
-
|
|
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
|
-
|
|
741
|
+
try {
|
|
742
|
+
runsection("prepareAuth", (args) -> {
|
|
743
|
+
Context ctx = OmniResolver.omniCtx(args[0], authClient, authUtility);
|
|
691
744
|
|
|
692
|
-
|
|
745
|
+
authUtility.prepareAuth.apply(ctx);
|
|
693
746
|
|
|
694
|
-
|
|
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
|