@voxgig/sdkgen 4.8.2 → 4.9.0
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/action/doctor.d.ts +1 -0
- package/dist/action/doctor.js +50 -2
- package/dist/action/doctor.js.map +1 -1
- package/dist/helpers/naming.js +4 -2
- package/dist/helpers/naming.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/model/sdkgen.aon +21 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/secrets.aon +120 -18
- package/project/.sdk/model/target/c.aon +10 -0
- package/project/.sdk/model/target/clojure.aon +13 -0
- package/project/.sdk/model/target/cpp.aon +10 -0
- package/project/.sdk/model/target/csharp.aon +9 -0
- package/project/.sdk/model/target/dart.aon +10 -0
- package/project/.sdk/model/target/elixir.aon +9 -0
- package/project/.sdk/model/target/go.aon +13 -0
- package/project/.sdk/model/target/java.aon +9 -0
- package/project/.sdk/model/target/js.aon +7 -0
- package/project/.sdk/model/target/kotlin.aon +9 -0
- package/project/.sdk/model/target/lean.aon +17 -0
- package/project/.sdk/model/target/lua.aon +8 -0
- package/project/.sdk/model/target/ocaml.aon +9 -0
- package/project/.sdk/model/target/perl.aon +8 -0
- package/project/.sdk/model/target/php.aon +8 -0
- package/project/.sdk/model/target/py.aon +13 -0
- package/project/.sdk/model/target/rb.aon +8 -0
- package/project/.sdk/model/target/rust.aon +10 -0
- package/project/.sdk/model/target/scala.aon +21 -2
- package/project/.sdk/model/target/swift.aon +18 -0
- package/project/.sdk/model/target/ts.aon +13 -0
- package/project/.sdk/model/target/zig.aon +9 -0
- package/project/.sdk/src/cmp/clojure/Package_clojure.ts +9 -2
- package/project/.sdk/src/cmp/cpp/Gitignore_cpp.ts +3 -0
- package/project/.sdk/src/cmp/dart/Test_dart.ts +2 -0
- package/project/.sdk/src/cmp/elixir/Package_elixir.ts +4 -1
- package/project/.sdk/src/cmp/go/Config_go.ts +59 -2
- package/project/.sdk/src/cmp/go/Main_go.ts +8 -2
- package/project/.sdk/src/cmp/java/Test_java.ts +6 -5
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +6 -5
- package/project/.sdk/src/cmp/lean/Package_lean.ts +23 -0
- package/project/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +1 -0
- package/project/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +1 -0
- package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +7 -3
- package/project/.sdk/src/cmp/py/Config_py.ts +82 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +44 -0
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +2 -0
- package/project/.sdk/src/cmp/swift/Main_swift.ts +10 -0
- package/project/.sdk/src/cmp/swift/Package_swift.ts +13 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +42 -17
- package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +13 -1
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +13 -1
- package/project/.sdk/src/cmp/zig/Package_zig.ts +15 -11
- package/project/.sdk/src/cmp/zig/fragment/Entity.fragment.zig +3 -3
- package/project/.sdk/src/cmp/zig/fragment/EntityListOp.fragment.zig +3 -3
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +3 -3
- package/project/.sdk/src/cmp/zig/utility_zig.ts +50 -1
- package/project/.sdk/tm/c/Makefile +38 -3
- package/project/.sdk/tm/c/tests/omni_resolver.h +361 -0
- package/project/.sdk/tm/c/tests/omni_smoke_test.c +237 -0
- package/project/.sdk/tm/c/tests/primary_corpus_test.c +205 -284
- package/project/.sdk/tm/c/tests/struct_corpus_test.c +292 -212
- package/project/.sdk/tm/c/tests/vendor/omni/json.c +811 -0
- package/project/.sdk/tm/c/tests/vendor/omni/omni.h +240 -0
- package/project/.sdk/tm/c/tests/vendor/omni/runner.c +940 -0
- package/project/.sdk/tm/c/tests/vendor/omni/util.c +351 -0
- package/project/.sdk/tm/c/utility/struct/utility.c +7 -2
- package/project/.sdk/tm/clojure/test/sdk/test/omni.clj +401 -0
- package/project/.sdk/tm/clojure/test/sdk/test/omni_smoke.clj +81 -0
- package/project/.sdk/tm/clojure/test/sdk/test/primary.clj +92 -58
- package/project/.sdk/tm/clojure/test/sdk/test/struct_corpus.clj +122 -149
- package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +8 -3
- package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/json.clj +145 -0
- package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/runner.clj +445 -0
- package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/util.clj +145 -0
- package/project/.sdk/tm/cpp/Makefile +5 -1
- package/project/.sdk/tm/cpp/test/omni_resolver.hpp +473 -0
- package/project/.sdk/tm/cpp/test/omni_smoke_test.cpp +209 -0
- package/project/.sdk/tm/cpp/test/primary_utility_test.cpp +243 -184
- package/project/.sdk/tm/cpp/test/runner_support.hpp +68 -181
- package/project/.sdk/tm/cpp/test/struct_corpus_test.cpp +183 -56
- package/project/.sdk/tm/cpp/test/vendor/omni/json.hpp +406 -0
- package/project/.sdk/tm/cpp/test/vendor/omni/omni.hpp +751 -0
- package/project/.sdk/tm/cpp/test/vendor/omni/util.hpp +256 -0
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +7 -1
- package/project/.sdk/tm/csharp/test/OmniResolver.cs +491 -0
- package/project/.sdk/tm/csharp/test/OmniSmokeTest.cs +106 -0
- package/project/.sdk/tm/csharp/test/PrimaryUtilityTest.cs +299 -316
- package/project/.sdk/tm/csharp/test/Runner.cs +104 -269
- package/project/.sdk/tm/csharp/test/StructUtilityTest.cs +535 -724
- package/project/.sdk/tm/csharp/test/vendor/omni/Runner.cs +864 -0
- package/project/.sdk/tm/csharp/test/vendor/omni/Util.cs +347 -0
- package/project/.sdk/tm/csharp/utility/MakeSpec.cs +3 -1
- package/project/.sdk/tm/csharp/utility/PrepareMethod.cs +6 -1
- package/project/.sdk/tm/csharp/utility/struct/Struct.cs +21 -5
- package/project/.sdk/tm/dart/test/omni.dart +520 -0
- package/project/.sdk/tm/dart/test/omni_smoke_test.dart +143 -0
- package/project/.sdk/tm/dart/test/primary_test.dart +86 -28
- package/project/.sdk/tm/dart/test/struct_test.dart +438 -11
- package/project/.sdk/tm/dart/test/vendor/omni/omni.dart +7 -0
- package/project/.sdk/tm/dart/test/vendor/omni/runner.dart +653 -0
- package/project/.sdk/tm/dart/test/vendor/omni/util.dart +232 -0
- package/project/.sdk/tm/elixir/Makefile +11 -3
- package/project/.sdk/tm/elixir/test/omni_smoke_test.exs +117 -0
- package/project/.sdk/tm/elixir/test/primary_utility_test.exs +341 -16
- package/project/.sdk/tm/elixir/test/struct_corpus_test.exs +554 -9
- package/project/.sdk/tm/elixir/test/support/omni.ex +377 -0
- package/project/.sdk/tm/elixir/test/vendor/omni/json.ex +122 -0
- package/project/.sdk/tm/elixir/test/vendor/omni/runner.ex +623 -0
- package/project/.sdk/tm/elixir/test/vendor/omni/util.ex +187 -0
- package/project/.sdk/tm/go/core/context.go +32 -16
- package/project/.sdk/tm/go/feature/secrets/plugin/capability.go +261 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/catalog.go +78 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/config.go +652 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/depend.go +250 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/env.go +194 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/export.go +80 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/graph.go +238 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/host.go +1575 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/order.go +248 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/point.go +184 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/ref.go +123 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/resolve.go +79 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/types.go +296 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/util.go +87 -0
- package/project/.sdk/tm/go/feature/secrets/plugin/version.go +161 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/aws/aws.go +292 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/aws/sigv4.go +201 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/azuresecrets/azuresecrets.go +187 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/boru/boru.go +184 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/doppler/doppler.go +114 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/gcpsecrets/gcpsecrets.go +156 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/hashicorp/hashicorp.go +208 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/httpjson/httpjson.go +241 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/infisical/infisical.go +150 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/onepassword/onepassword.go +152 -0
- package/project/.sdk/tm/go/feature/secrets/plugins/secretspec/secretspec.go +143 -0
- package/project/.sdk/tm/go/feature/secrets/sekreto/addr.go +120 -0
- package/project/.sdk/tm/go/feature/secrets/sekreto/providers.go +442 -0
- package/project/.sdk/tm/go/feature/secrets/sekreto/sekreto.go +714 -0
- package/project/.sdk/tm/go/feature/secrets_feature.go +675 -0
- package/project/.sdk/tm/go/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/go/test/feature/secrets/secrets_feature_test.go +799 -0
- package/project/.sdk/tm/go/test/omni/omni.go +902 -0
- package/project/.sdk/tm/go/test/omni/util.go +381 -0
- package/project/.sdk/tm/go/test/omniresolver_test.go +748 -0
- package/project/.sdk/tm/go/test/omnismoke_test.go +112 -0
- package/project/.sdk/tm/go/test/primary_utility_test.go +123 -191
- package/project/.sdk/tm/go/test/struct_utility_test.go +323 -16
- package/project/.sdk/tm/go/test/testsupport_test.go +294 -0
- package/project/.sdk/tm/go/utility/make_options.go +8 -1
- package/project/.sdk/tm/go/utility/prepare_method.go +5 -1
- package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +2 -2
- package/project/.sdk/tm/java/test/OmniResolver.java +355 -0
- package/project/.sdk/tm/java/test/OmniSmokeTest.java +99 -0
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +254 -280
- package/project/.sdk/tm/java/test/RunnerSupport.java +9 -194
- package/project/.sdk/tm/java/test/StructCorpusTest.java +119 -42
- package/project/.sdk/tm/java/test/vendor/omni/Json.java +287 -0
- package/project/.sdk/tm/java/test/vendor/omni/Runner.java +851 -0
- package/project/.sdk/tm/java/test/vendor/omni/Util.java +322 -0
- package/project/.sdk/tm/java/utility/MakeSpec.java +3 -1
- package/project/.sdk/tm/java/utility/PrepareMethod.java +5 -5
- package/project/.sdk/tm/java/utility/struct/Struct.java +17 -4
- package/project/.sdk/tm/js/src/utility/StructUtility.js +3 -3
- package/project/.sdk/tm/js/test/omni.js +251 -0
- package/project/.sdk/tm/js/test/omni.test.js +105 -0
- package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +6 -2
- package/project/.sdk/tm/js/test/utility/StructUtility.test.js +27 -2
- package/project/.sdk/tm/js/test/vendor/omni/index.js +77 -0
- package/project/.sdk/tm/js/test/vendor/omni/runner.js +628 -0
- package/project/.sdk/tm/js/test/vendor/omni/util.js +229 -0
- package/project/.sdk/tm/kotlin/core/Utility.kt +1 -1
- package/project/.sdk/tm/kotlin/test/OmniResolver.kt +343 -0
- package/project/.sdk/tm/kotlin/test/OmniSmokeTest.kt +100 -0
- package/project/.sdk/tm/kotlin/test/PrimaryUtilityTest.kt +244 -262
- package/project/.sdk/tm/kotlin/test/RunnerSupport.kt +9 -178
- package/project/.sdk/tm/kotlin/test/StructCorpusTest.kt +121 -32
- package/project/.sdk/tm/kotlin/test/vendor/omni/Json.kt +264 -0
- package/project/.sdk/tm/kotlin/test/vendor/omni/Runner.kt +615 -0
- package/project/.sdk/tm/kotlin/test/vendor/omni/Util.kt +139 -0
- package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +6 -3
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +6 -6
- package/project/.sdk/tm/kotlin/utility/struct/Struct.kt +18 -2
- package/project/.sdk/tm/lean/Makefile +12 -5
- package/project/.sdk/tm/lean/src/SdkUtility.lean +14 -1
- package/project/.sdk/tm/lean/test/OmniResolver.lean +358 -0
- package/project/.sdk/tm/lean/test/OmniSmoke.lean +174 -0
- package/project/.sdk/tm/lean/test/StructCorpus.lean +245 -574
- package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +155 -291
- package/project/.sdk/tm/lean/test/vendor/omni/Omni.lean +997 -0
- package/project/.sdk/tm/lua/test/omni.lua +456 -0
- package/project/.sdk/tm/lua/test/omni_smoke_test.lua +103 -0
- package/project/.sdk/tm/lua/test/primary_utility_test.lua +250 -509
- package/project/.sdk/tm/lua/test/struct_utility_test.lua +161 -70
- package/project/.sdk/tm/lua/test/vendor/omni/json.lua +268 -0
- package/project/.sdk/tm/lua/test/vendor/omni/regex.lua +385 -0
- package/project/.sdk/tm/lua/test/vendor/omni/runner.lua +641 -0
- package/project/.sdk/tm/lua/test/vendor/omni/util.lua +282 -0
- package/project/.sdk/tm/lua/utility/prepare_method.lua +5 -1
- package/project/.sdk/tm/lua/utility/struct/regex.lua +663 -0
- package/project/.sdk/tm/lua/utility/struct/struct.lua +660 -420
- package/project/.sdk/tm/ocaml/Makefile +37 -12
- package/project/.sdk/tm/ocaml/test/omni_resolver.ml +284 -0
- package/project/.sdk/tm/ocaml/test/omni_smoke_test.ml +169 -0
- package/project/.sdk/tm/ocaml/test/primary_utility_test.ml +84 -100
- package/project/.sdk/tm/ocaml/test/struct_corpus.ml +225 -201
- package/project/.sdk/tm/ocaml/test/vendor/omni/omni.ml +1157 -0
- package/project/.sdk/tm/perl/lib/Voxgig/Struct.pm +88 -12
- package/project/.sdk/tm/perl/t/omni.pm +647 -0
- package/project/.sdk/tm/perl/t/omni_smoke.t +87 -0
- package/project/.sdk/tm/perl/t/primary_utility.t +189 -487
- package/project/.sdk/tm/perl/t/struct_utility.t +455 -389
- package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni/Runner.pm +649 -0
- package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni/Util.pm +253 -0
- package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni.pm +24 -0
- package/project/.sdk/tm/perl/utility/prepare_method.pm +7 -2
- package/project/.sdk/tm/php/test/Omni.php +691 -0
- package/project/.sdk/tm/php/test/OmniSmokeTest.php +93 -0
- package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +210 -571
- package/project/.sdk/tm/php/test/StructUtilityTest.php +320 -342
- package/project/.sdk/tm/php/test/vendor/omni/Runner.php +644 -0
- package/project/.sdk/tm/php/test/vendor/omni/Util.php +283 -0
- package/project/.sdk/tm/php/utility/MakeSpec.php +10 -3
- package/project/.sdk/tm/php/utility/PrepareBody.php +7 -1
- package/project/.sdk/tm/php/utility/PrepareMethod.php +6 -2
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/__init__.py +36 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/capability.py +141 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/catalog.py +50 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/config.py +333 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/depend.py +198 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/env.py +147 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/export.py +52 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/graph.py +139 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/host.py +1053 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/order.py +171 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/point.py +95 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/ref.py +116 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/resolve.py +59 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/types.py +87 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/version.py +112 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/__init__.py +76 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/addr.py +116 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/aws.py +192 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/azuresecrets.py +120 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/boru.py +140 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/doppler.py +62 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/gcpsecrets.py +101 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/hashicorp.py +151 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/httpjson.py +115 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/infisical.py +90 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/onepassword.py +101 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/secretspec.py +119 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/sigv4.py +125 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/providers.py +211 -0
- package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/sekreto.py +475 -0
- package/project/.sdk/tm/py/pkg/feature/secrets_feature.py +467 -0
- package/project/.sdk/tm/py/pkg/utility/prepare_method.py +5 -1
- package/project/.sdk/tm/py/pkg/utility/voxgig_struct/__init__.py +0 -2
- package/project/.sdk/tm/py/pkg/utility/voxgig_struct/voxgig_struct.py +780 -529
- package/project/.sdk/tm/py/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/py/test/feature/__init__.py +0 -0
- package/project/.sdk/tm/py/test/feature/secrets/__init__.py +0 -0
- package/project/.sdk/tm/py/test/feature/secrets/test_secrets.py +337 -0
- package/project/.sdk/tm/py/test/omni.py +415 -0
- package/project/.sdk/tm/py/test/test_omni_smoke.py +74 -0
- package/project/.sdk/tm/py/test/test_primary_utility.py +144 -668
- package/project/.sdk/tm/py/test/test_struct_utility.py +74 -27
- package/project/.sdk/tm/py/test/voxgig_omni/__init__.py +62 -0
- package/project/.sdk/tm/py/test/voxgig_omni/runner.py +580 -0
- package/project/.sdk/tm/py/test/voxgig_omni/util.py +252 -0
- package/project/.sdk/tm/rb/test/omni.rb +505 -0
- package/project/.sdk/tm/rb/test/omni_smoke_test.rb +72 -0
- package/project/.sdk/tm/rb/test/primary_utility_test.rb +174 -560
- package/project/.sdk/tm/rb/test/struct_utility_test.rb +13 -7
- package/project/.sdk/tm/rb/test/vendor/omni/runner.rb +493 -0
- package/project/.sdk/tm/rb/test/vendor/omni/util.rb +199 -0
- package/project/.sdk/tm/rb/test/vendor/omni/voxgig_omni.rb +15 -0
- package/project/.sdk/tm/rb/utility/prepare_method.rb +5 -1
- package/project/.sdk/tm/rust/tests/common/mod.rs +19 -242
- package/project/.sdk/tm/rust/tests/omni_resolver/mod.rs +888 -0
- package/project/.sdk/tm/rust/tests/omni_smoke_test.rs +286 -0
- package/project/.sdk/tm/rust/tests/primary_utility_test.rs +318 -312
- package/project/.sdk/tm/rust/tests/struct_utility_test.rs +66 -28
- package/project/.sdk/tm/rust/tests/vendor/omni/json.rs +412 -0
- package/project/.sdk/tm/rust/tests/vendor/omni/mod.rs +38 -0
- package/project/.sdk/tm/rust/tests/vendor/omni/regex.rs +392 -0
- package/project/.sdk/tm/rust/tests/vendor/omni/runner.rs +1001 -0
- package/project/.sdk/tm/rust/tests/vendor/omni/util.rs +161 -0
- package/project/.sdk/tm/rust/utility/prepare_method.rs +5 -1
- package/project/.sdk/tm/scala/Makefile +16 -6
- package/project/.sdk/tm/scala/sdktest/OmniResolver.scala +497 -0
- package/project/.sdk/tm/scala/sdktest/OmniSmoke.scala +192 -0
- package/project/.sdk/tm/scala/sdktest/PrimaryCorpus.scala +47 -19
- package/project/.sdk/tm/scala/sdktest/StructCorpus.scala +0 -0
- package/project/.sdk/tm/scala/sdktest/vendor/omni/Json.scala +229 -0
- package/project/.sdk/tm/scala/sdktest/vendor/omni/Runner.scala +552 -0
- package/project/.sdk/tm/scala/sdktest/vendor/omni/Util.scala +104 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Constants.swift +10 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Inject.swift +28 -5
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Injection.swift +5 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Minor.swift +4 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Validate.swift +15 -5
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +6 -1
- package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/OmniResolver.swift +474 -0
- package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/OmniSmokeTest.swift +107 -0
- package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/PrimaryUtilityTest.swift +141 -178
- package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/Runner.swift +17 -113
- package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/StructUtilityTest.swift +295 -446
- package/project/.sdk/tm/swift/Tests/vendor/omni/Json.swift +364 -0
- package/project/.sdk/tm/swift/Tests/vendor/omni/Runner.swift +730 -0
- package/project/.sdk/tm/swift/Tests/vendor/omni/Util.swift +162 -0
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +7 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Capability.ts +148 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Catalog.ts +55 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Config.ts +344 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Depend.ts +205 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Env.ts +138 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Export.ts +46 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/FeatureHost.ts +349 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Graph.ts +181 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Host.ts +1132 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Order.ts +177 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Point.ts +119 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Ref.ts +102 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Resolve.ts +61 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Types.ts +112 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/Version.ts +100 -0
- package/project/.sdk/tm/ts/src/feature/secrets/plugin/index.ts +37 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +163 -30
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +27 -22
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/aws.ts +26 -36
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/azuresecrets.ts +19 -21
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/boru.ts +36 -17
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/doppler.ts +14 -21
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/gcpsecrets.ts +27 -27
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/hashicorp.ts +25 -39
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/plugins/httpjson.ts +99 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/infisical.ts +14 -16
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/onepassword.ts +15 -22
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/plugins/secretspec.ts +98 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{Sigv4.ts → plugins/sigv4.ts} +7 -6
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +97 -23
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/builtin.ts +37 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +4 -17
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +4 -16
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +11 -25
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +6 -23
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +106 -39
- package/project/.sdk/tm/ts/src/utility/StructUtility.ts +3 -3
- package/project/.sdk/tm/ts/test/omni.ts +104 -32
- package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +27 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +32 -5
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +2 -2
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +2 -2
- package/project/.sdk/tm/zig/Makefile +15 -1
- package/project/.sdk/tm/zig/build.zig +23 -5
- package/project/.sdk/tm/zig/core/helpers.zig +52 -11
- package/project/.sdk/tm/zig/core/mem.zig +17 -1
- package/project/.sdk/tm/zig/core/utility.zig +23 -19
- package/project/.sdk/tm/zig/feature/audit.zig +2 -2
- package/project/.sdk/tm/zig/feature/cache.zig +2 -2
- package/project/.sdk/tm/zig/feature/debug.zig +5 -5
- package/project/.sdk/tm/zig/feature/netsim.zig +2 -2
- package/project/.sdk/tm/zig/feature/paging.zig +2 -2
- package/project/.sdk/tm/zig/feature/proxy.zig +15 -8
- package/project/.sdk/tm/zig/feature/streaming.zig +5 -5
- package/project/.sdk/tm/zig/feature/support.zig +3 -3
- package/project/.sdk/tm/zig/feature/telemetry.zig +5 -5
- package/project/.sdk/tm/zig/feature/test.zig +10 -5
- package/project/.sdk/tm/zig/test/fh.zig +10 -10
- package/project/.sdk/tm/zig/test/gotcha_test.zig +5 -5
- package/project/.sdk/tm/zig/test/omniresolver.zig +515 -0
- package/project/.sdk/tm/zig/test/omnismoke_test.zig +284 -0
- package/project/.sdk/tm/zig/test/pipeline_test.zig +8 -8
- package/project/.sdk/tm/zig/test/primary_utility_test.zig +145 -155
- package/project/.sdk/tm/zig/test/struct_corpus.zig +264 -187
- package/project/.sdk/tm/zig/test/vendor/omni/omni.zig +1314 -0
- package/project/.sdk/tm/zig/test/vendor/omni/regex.zig +446 -0
- package/project/.sdk/tm/zig/utility/voxgigstruct/regex.zig +7 -4
- package/project/.sdk/tm/zig/utility/voxgigstruct/struct.zig +356 -157
- package/project/sdkgen-package.json +1 -1
- package/src/action/doctor.ts +66 -2
- package/src/helpers/naming.ts +4 -2
- package/project/.sdk/tm/c/tests/runner.h +0 -274
- package/project/.sdk/tm/cpp/test/struct_runner.hpp +0 -314
- package/project/.sdk/tm/csharp/test/StructRunner.cs +0 -276
- package/project/.sdk/tm/dart/test/runner.dart +0 -406
- package/project/.sdk/tm/dart/test/struct_corpus.dart +0 -531
- package/project/.sdk/tm/elixir/test/support/struct_corpus.ex +0 -1088
- package/project/.sdk/tm/go/test/runner_test.go +0 -629
- package/project/.sdk/tm/go/test/struct_runner_test.go +0 -1094
- package/project/.sdk/tm/java/test/StructRunner.java +0 -281
- package/project/.sdk/tm/js/test/runner.js +0 -387
- package/project/.sdk/tm/kotlin/test/StructRunner.kt +0 -177
- package/project/.sdk/tm/lua/test/struct_runner.lua +0 -602
- package/project/.sdk/tm/ocaml/test/corpus_runner.ml +0 -185
- package/project/.sdk/tm/perl/t/struct_runner.pm +0 -275
- package/project/.sdk/tm/php/test/StructRunner.php +0 -275
- package/project/.sdk/tm/py/test/struct_runner.py +0 -411
- package/project/.sdk/tm/rb/test/struct_runner.rb +0 -309
- package/project/.sdk/tm/rust/tests/struct_runner/mod.rs +0 -424
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +0 -90
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +0 -66
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +0 -188
- package/project/.sdk/tm/zig/test/struct_runner.zig +0 -464
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
// Behavioural tests for the secrets feature (vendored @voxgig/sekreto) -
|
|
2
|
+
// the go port of tm/ts/test/feature/secrets/Secrets.test.ts.
|
|
3
|
+
//
|
|
4
|
+
// The contract under test: the `apikey` OPTION keeps its exact old meaning
|
|
5
|
+
// and always wins, because SecretsFeature places it FIRST in the provider
|
|
6
|
+
// chain (a `memory` store named `options`) - explicit-beats-lookup falls
|
|
7
|
+
// out of sekreto's first-hit rule rather than from special-case logic.
|
|
8
|
+
// With the feature inactive nothing changes at all. With it active and the
|
|
9
|
+
// option unset, the chain (env, a custom provider, a vault) supplies the
|
|
10
|
+
// credential instead.
|
|
11
|
+
//
|
|
12
|
+
// This file lives in the test `feature/` container on purpose: `target
|
|
13
|
+
// add` trims it, along with the feature source and the vendored library,
|
|
14
|
+
// for a project whose model does not select `secrets`.
|
|
15
|
+
//
|
|
16
|
+
// The feature is CONSTRUCTED DIRECTLY and handed in through the
|
|
17
|
+
// `extend` option, so these tests hold in any generated tree - whether or
|
|
18
|
+
// not the project's model activated the feature (activation only changes
|
|
19
|
+
// whether config.go registers a constructor for it). The credential is
|
|
20
|
+
// asserted ON THE WIRE: a live-mode client with a recording system.fetch,
|
|
21
|
+
// driven through a real entity operation, which is what exercises the
|
|
22
|
+
// PreSpec resolution seam. (See the migration guide: an options-level
|
|
23
|
+
// assertion passes for a port that never consults the value.)
|
|
24
|
+
|
|
25
|
+
package secretstest
|
|
26
|
+
|
|
27
|
+
import (
|
|
28
|
+
"encoding/json"
|
|
29
|
+
"fmt"
|
|
30
|
+
"net/http"
|
|
31
|
+
"net/http/httptest"
|
|
32
|
+
"os"
|
|
33
|
+
"reflect"
|
|
34
|
+
"strings"
|
|
35
|
+
"sync"
|
|
36
|
+
"testing"
|
|
37
|
+
"time"
|
|
38
|
+
"unicode"
|
|
39
|
+
|
|
40
|
+
sdk "GOMODULE"
|
|
41
|
+
feat "GOMODULE/feature"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const envprefix = "PROJECTENV_TEST_SECRETS_"
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------
|
|
47
|
+
// The recording transport: system.fetch for a LIVE client, scripting one
|
|
48
|
+
// status per API call (the last repeating) and a token endpoint for the
|
|
49
|
+
// exchange tests.
|
|
50
|
+
|
|
51
|
+
type wirecall struct {
|
|
52
|
+
url string
|
|
53
|
+
auth string
|
|
54
|
+
has bool
|
|
55
|
+
body string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type wire struct {
|
|
59
|
+
mu sync.Mutex
|
|
60
|
+
calls []wirecall
|
|
61
|
+
apistatus []int
|
|
62
|
+
tokens []string
|
|
63
|
+
tokenpath string
|
|
64
|
+
respfield string
|
|
65
|
+
issued int
|
|
66
|
+
apicalls int
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func makewire() *wire {
|
|
70
|
+
return &wire{
|
|
71
|
+
apistatus: []int{200},
|
|
72
|
+
tokens: []string{"ACCESS01", "ACCESS02", "ACCESS03"},
|
|
73
|
+
tokenpath: "auth/token",
|
|
74
|
+
respfield: "access_token",
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func (w *wire) fetch(url string, fetchdef map[string]any) (map[string]any, error) {
|
|
79
|
+
w.mu.Lock()
|
|
80
|
+
defer w.mu.Unlock()
|
|
81
|
+
|
|
82
|
+
auth := ""
|
|
83
|
+
has := false
|
|
84
|
+
if headers, is := fetchdef["headers"].(map[string]any); is {
|
|
85
|
+
if v, found := headers["authorization"]; found {
|
|
86
|
+
auth, _ = v.(string)
|
|
87
|
+
has = true
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
body, _ := fetchdef["body"].(string)
|
|
91
|
+
w.calls = append(w.calls, wirecall{url: url, auth: auth, has: has, body: body})
|
|
92
|
+
|
|
93
|
+
if strings.HasSuffix(url, "/"+w.tokenpath) {
|
|
94
|
+
token := w.tokens[min(w.issued, len(w.tokens)-1)]
|
|
95
|
+
w.issued++
|
|
96
|
+
payload := map[string]any{w.respfield: token}
|
|
97
|
+
return map[string]any{
|
|
98
|
+
"status": 200, "statusText": "OK",
|
|
99
|
+
"headers": map[string]any{},
|
|
100
|
+
"json": (func() any)(func() any { return payload }),
|
|
101
|
+
}, nil
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
status := w.apistatus[min(w.apicalls, len(w.apistatus)-1)]
|
|
105
|
+
w.apicalls++
|
|
106
|
+
payload := map[string]any{"ok": status < 400}
|
|
107
|
+
return map[string]any{
|
|
108
|
+
"status": status, "statusText": "X",
|
|
109
|
+
"headers": map[string]any{},
|
|
110
|
+
"json": (func() any)(func() any { return payload }),
|
|
111
|
+
}, nil
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func min(a, b int) int {
|
|
115
|
+
if a < b {
|
|
116
|
+
return a
|
|
117
|
+
}
|
|
118
|
+
return b
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// api returns the recorded calls that did NOT go to the token endpoint.
|
|
122
|
+
func (w *wire) api() []wirecall {
|
|
123
|
+
w.mu.Lock()
|
|
124
|
+
defer w.mu.Unlock()
|
|
125
|
+
out := []wirecall{}
|
|
126
|
+
for _, c := range w.calls {
|
|
127
|
+
if !strings.HasSuffix(c.url, "/"+w.tokenpath) {
|
|
128
|
+
out = append(out, c)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return out
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func (w *wire) token() []wirecall {
|
|
135
|
+
w.mu.Lock()
|
|
136
|
+
defer w.mu.Unlock()
|
|
137
|
+
out := []wirecall{}
|
|
138
|
+
for _, c := range w.calls {
|
|
139
|
+
if strings.HasSuffix(c.url, "/"+w.tokenpath) {
|
|
140
|
+
out = append(out, c)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return out
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------------------------------------------------------------------
|
|
147
|
+
// Support.
|
|
148
|
+
|
|
149
|
+
// The Authorization header carries the SPEC's credential prefix, which a
|
|
150
|
+
// TEMPLATE cannot know - so assert on the CREDENTIAL and let the prefix be
|
|
151
|
+
// whatever this SDK's API declares.
|
|
152
|
+
func credentialIs(t *testing.T, header string, token string) {
|
|
153
|
+
t.Helper()
|
|
154
|
+
if header != token && !strings.HasSuffix(header, " "+token) {
|
|
155
|
+
t.Fatalf("expected the authorization header to carry %s, got: %q", token, header)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// secretsClient builds a LIVE client carrying the secrets feature via the
|
|
160
|
+
// `extend` seam, wired to the recording transport.
|
|
161
|
+
func secretsClient(w *wire, sdkopts map[string]any) *sdk.ProjectNameSDK {
|
|
162
|
+
opts := map[string]any{
|
|
163
|
+
"base": "http://secrets.test/api",
|
|
164
|
+
"system": map[string]any{"fetch": w.fetch},
|
|
165
|
+
}
|
|
166
|
+
for k, v := range sdkopts {
|
|
167
|
+
opts[k] = v
|
|
168
|
+
}
|
|
169
|
+
return withSecrets(func(extend bool) *sdk.ProjectNameSDK {
|
|
170
|
+
if extend {
|
|
171
|
+
opts["extend"] = []any{feat.NewSecretsFeature()}
|
|
172
|
+
}
|
|
173
|
+
return sdk.NewProjectNameSDK(opts)
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// withSecrets constructs the client and ADOPTS the feature via `extend`
|
|
178
|
+
// ONLY when the generated config did not already install it - when this
|
|
179
|
+
// SDK was generated with `secrets` model-active, the ordinary factory
|
|
180
|
+
// path builds the instance, and adding a second via extend would DOUBLE
|
|
181
|
+
// the feature: two transport wraps, two resolutions, and a token
|
|
182
|
+
// purchase the assertions cannot account for. (The py harness guards the
|
|
183
|
+
// same way via _has_feature.)
|
|
184
|
+
func withSecrets(build func(extend bool) *sdk.ProjectNameSDK) *sdk.ProjectNameSDK {
|
|
185
|
+
client := build(false)
|
|
186
|
+
if nil == secretsFeatureOf(client) {
|
|
187
|
+
client = build(true)
|
|
188
|
+
}
|
|
189
|
+
return client
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func secretsOpts(extra map[string]any) map[string]any {
|
|
193
|
+
fopts := map[string]any{
|
|
194
|
+
"active": true,
|
|
195
|
+
"providers": []any{
|
|
196
|
+
map[string]any{"kind": "env", "prefix": envprefix},
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
for k, v := range extra {
|
|
200
|
+
fopts[k] = v
|
|
201
|
+
}
|
|
202
|
+
return map[string]any{"secrets": fopts}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// secretsFeatureOf digs the live feature back out of the client, for
|
|
206
|
+
// assertions against its Sekreto instance.
|
|
207
|
+
func secretsFeatureOf(client *sdk.ProjectNameSDK) *feat.SecretsFeature {
|
|
208
|
+
for _, f := range client.Features {
|
|
209
|
+
if sf, is := f.(*feat.SecretsFeature); is {
|
|
210
|
+
return sf
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return nil
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// driveEntityOpUntil performs real entity operations - which is what runs
|
|
217
|
+
// the PreSpec hook - until `stop` reports the observable state a test is
|
|
218
|
+
// waiting for. Each op's own outcome is irrelevant (no seeded data, a
|
|
219
|
+
// scripted response); an op the API does not define fails BEFORE the
|
|
220
|
+
// PreSpec hook, which is why several may need driving. Entity names come
|
|
221
|
+
// from the SDK's own config, because this file is a TEMPLATE and no
|
|
222
|
+
// project's entity names are known here.
|
|
223
|
+
func driveEntityOpUntil(t *testing.T, client *sdk.ProjectNameSDK, what string, stop func() bool) {
|
|
224
|
+
t.Helper()
|
|
225
|
+
|
|
226
|
+
entities, _ := client.OptionsMap()["entity"].(map[string]any)
|
|
227
|
+
clientval := reflect.ValueOf(client)
|
|
228
|
+
|
|
229
|
+
for name := range entities {
|
|
230
|
+
runes := []rune(name)
|
|
231
|
+
runes[0] = unicode.ToUpper(runes[0])
|
|
232
|
+
accessor := clientval.MethodByName(string(runes))
|
|
233
|
+
if !accessor.IsValid() || 1 != accessor.Type().NumIn() {
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
ent := accessor.Call([]reflect.Value{
|
|
238
|
+
reflect.Zero(accessor.Type().In(0)),
|
|
239
|
+
})[0]
|
|
240
|
+
|
|
241
|
+
for _, opname := range []string{"List", "Load"} {
|
|
242
|
+
op := ent.MethodByName(opname)
|
|
243
|
+
if !op.IsValid() {
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
in := make([]reflect.Value, op.Type().NumIn())
|
|
247
|
+
for i := range in {
|
|
248
|
+
in[i] = reflect.Zero(op.Type().In(i))
|
|
249
|
+
}
|
|
250
|
+
op.Call(in)
|
|
251
|
+
|
|
252
|
+
if stop() {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
t.Fatalf("no entity operation %s - nothing to assert on", what)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// driveEntityOp drives ops until one request reached the recorder.
|
|
262
|
+
func driveEntityOp(t *testing.T, client *sdk.ProjectNameSDK, w *wire) {
|
|
263
|
+
t.Helper()
|
|
264
|
+
before := len(w.api())
|
|
265
|
+
driveEntityOpUntil(t, client, "reached the transport", func() bool {
|
|
266
|
+
return before < len(w.api())
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------
|
|
271
|
+
// The feature-inactive baseline: bit-identical behaviour.
|
|
272
|
+
|
|
273
|
+
func TestSecretsInactive(t *testing.T) {
|
|
274
|
+
|
|
275
|
+
t.Run("apikey option behaves exactly as before", func(t *testing.T) {
|
|
276
|
+
client := sdk.TestSDK(nil, map[string]any{"apikey": "OPTKEY01"})
|
|
277
|
+
fetchdef, err := client.Prepare(map[string]any{"path": "/"})
|
|
278
|
+
if err != nil {
|
|
279
|
+
t.Fatalf("prepare failed: %v", err)
|
|
280
|
+
}
|
|
281
|
+
headers, _ := fetchdef["headers"].(map[string]any)
|
|
282
|
+
auth, _ := headers["authorization"].(string)
|
|
283
|
+
credentialIs(t, auth, "OPTKEY01")
|
|
284
|
+
|
|
285
|
+
if nil != secretsFeatureOf(client) {
|
|
286
|
+
t.Fatal("no model activation, no extend: the feature must not be installed")
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
t.Run("no apikey means no authorization header", func(t *testing.T) {
|
|
291
|
+
client := sdk.TestSDK(nil, nil)
|
|
292
|
+
fetchdef, err := client.Prepare(map[string]any{"path": "/"})
|
|
293
|
+
if err != nil {
|
|
294
|
+
t.Fatalf("prepare failed: %v", err)
|
|
295
|
+
}
|
|
296
|
+
headers, _ := fetchdef["headers"].(map[string]any)
|
|
297
|
+
if _, has := headers["authorization"]; has {
|
|
298
|
+
t.Fatalf("unexpected authorization header: %v", headers["authorization"])
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ---------------------------------------------------------------------
|
|
304
|
+
// Active: the provider chain, driven through real entity operations.
|
|
305
|
+
|
|
306
|
+
func TestSecretsChain(t *testing.T) {
|
|
307
|
+
|
|
308
|
+
t.Run("apikey option still wins over the chain", func(t *testing.T) {
|
|
309
|
+
os.Setenv(envprefix+"APIKEY", "ENVKEY01")
|
|
310
|
+
defer os.Unsetenv(envprefix + "APIKEY")
|
|
311
|
+
|
|
312
|
+
w := makewire()
|
|
313
|
+
client := secretsClient(w, map[string]any{
|
|
314
|
+
"apikey": "OPTKEY01",
|
|
315
|
+
"feature": secretsOpts(nil),
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
driveEntityOp(t, client, w)
|
|
319
|
+
call := w.api()[0]
|
|
320
|
+
credentialIs(t, call.auth, "OPTKEY01")
|
|
321
|
+
|
|
322
|
+
// The explicit option is a real store, not a special case: a
|
|
323
|
+
// directed read names it like any other.
|
|
324
|
+
sf := secretsFeatureOf(client)
|
|
325
|
+
if nil == sf {
|
|
326
|
+
t.Fatal("the extend seam did not install the feature")
|
|
327
|
+
}
|
|
328
|
+
found, err := sf.Sekreto().GetFrom("options", "apikey")
|
|
329
|
+
if err != nil || "OPTKEY01" != found {
|
|
330
|
+
t.Fatalf("directed read of the options store: %q, %v", found, err)
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
t.Run("an omitted apikey defers to the chain at the transport seam", func(t *testing.T) {
|
|
335
|
+
os.Setenv(envprefix+"APIKEY", "ENVKEY02")
|
|
336
|
+
defer os.Unsetenv(envprefix + "APIKEY")
|
|
337
|
+
|
|
338
|
+
w := makewire()
|
|
339
|
+
client := secretsClient(w, map[string]any{"feature": secretsOpts(nil)})
|
|
340
|
+
|
|
341
|
+
// Before any op, nothing has been resolved.
|
|
342
|
+
if v, _ := client.OptionsMap()["apikey"].(string); "" != v {
|
|
343
|
+
t.Fatalf("apikey resolved before any operation: %q", v)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
driveEntityOp(t, client, w)
|
|
347
|
+
|
|
348
|
+
// Resolution happens AT THE TRANSPORT - the one seam every wire
|
|
349
|
+
// path crosses - so the credential is on the wire, not merely
|
|
350
|
+
// resolved. go holds it in FEATURE STATE and injects there; the
|
|
351
|
+
// options map is never mutated (it stays raced-read-safe for
|
|
352
|
+
// every concurrent operation), so the state assertion reads the
|
|
353
|
+
// feature.
|
|
354
|
+
credentialIs(t, w.api()[0].auth, "ENVKEY02")
|
|
355
|
+
if v := secretsFeatureOf(client).Credential(); "ENVKEY02" != v {
|
|
356
|
+
t.Fatalf("the entity op did not resolve the secret through PreSpec: %q", v)
|
|
357
|
+
}
|
|
358
|
+
if v, _ := client.OptionsMap()["apikey"].(string); "" != v {
|
|
359
|
+
t.Fatalf("the options map must stay unwritten (frozen after construction), got apikey=%q", v)
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
t.Run("custom provider objects are accepted verbatim", func(t *testing.T) {
|
|
364
|
+
asked := []string{}
|
|
365
|
+
w := makewire()
|
|
366
|
+
client := secretsClient(w, map[string]any{
|
|
367
|
+
"feature": map[string]any{"secrets": map[string]any{
|
|
368
|
+
"active": true,
|
|
369
|
+
"providers": []any{
|
|
370
|
+
&customProvider{
|
|
371
|
+
lookup: func(name string) (string, bool, error) {
|
|
372
|
+
asked = append(asked, name)
|
|
373
|
+
return "CUSTOM01", true, nil
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
}},
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
driveEntityOp(t, client, w)
|
|
381
|
+
credentialIs(t, w.api()[0].auth, "CUSTOM01")
|
|
382
|
+
if 1 > len(asked) || "apikey" != asked[0] {
|
|
383
|
+
t.Fatalf("the custom provider was asked %v, want [apikey ...]", asked)
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
t.Run("a miss everywhere leaves the header off", func(t *testing.T) {
|
|
388
|
+
os.Unsetenv(envprefix + "APIKEY")
|
|
389
|
+
|
|
390
|
+
w := makewire()
|
|
391
|
+
client := secretsClient(w, map[string]any{"feature": secretsOpts(nil)})
|
|
392
|
+
|
|
393
|
+
driveEntityOp(t, client, w)
|
|
394
|
+
call := w.api()[0]
|
|
395
|
+
if call.has {
|
|
396
|
+
t.Fatalf("a chain MISS must fall through to an unauthenticated request, got header %q", call.auth)
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
t.Run("a provider ERROR fails the op and nothing reaches the wire", func(t *testing.T) {
|
|
401
|
+
asked := false
|
|
402
|
+
w := makewire()
|
|
403
|
+
client := secretsClient(w, map[string]any{
|
|
404
|
+
"feature": map[string]any{"secrets": map[string]any{
|
|
405
|
+
"active": true,
|
|
406
|
+
"providers": []any{
|
|
407
|
+
&customProvider{
|
|
408
|
+
lookup: func(name string) (string, bool, error) {
|
|
409
|
+
asked = true
|
|
410
|
+
return "", false, fmt.Errorf("vault unreachable")
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
}},
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
driveEntityOpUntil(t, client, "consulted the chain", func() bool {
|
|
418
|
+
return asked
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
if 0 != len(w.api()) {
|
|
422
|
+
t.Fatalf("a broken vault must never yield a request: %d calls went out", len(w.api()))
|
|
423
|
+
}
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
t.Run("a provider recovers after a transient failure", func(t *testing.T) {
|
|
427
|
+
calls := 0
|
|
428
|
+
w := makewire()
|
|
429
|
+
client := secretsClient(w, map[string]any{
|
|
430
|
+
"feature": map[string]any{"secrets": map[string]any{
|
|
431
|
+
"active": true,
|
|
432
|
+
"providers": []any{
|
|
433
|
+
&customProvider{
|
|
434
|
+
lookup: func(name string) (string, bool, error) {
|
|
435
|
+
calls++
|
|
436
|
+
if 1 == calls {
|
|
437
|
+
return "", false, fmt.Errorf("vault unreachable")
|
|
438
|
+
}
|
|
439
|
+
return "RECOVERED01", true, nil
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
}},
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
// First op fails closed; a failed resolution is never cached, so
|
|
447
|
+
// the second op asks the chain again and succeeds.
|
|
448
|
+
driveEntityOpUntil(t, client, "consulted the chain", func() bool {
|
|
449
|
+
return 0 < calls
|
|
450
|
+
})
|
|
451
|
+
if 0 != len(w.api()) {
|
|
452
|
+
t.Fatal("the first op must not reach the wire")
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
driveEntityOp(t, client, w)
|
|
456
|
+
credentialIs(t, w.api()[0].auth, "RECOVERED01")
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
t.Run("auth nil suppresses the credential, chain or no chain", func(t *testing.T) {
|
|
460
|
+
os.Setenv(envprefix+"APIKEY", "ENVKEY03")
|
|
461
|
+
defer os.Unsetenv(envprefix + "APIKEY")
|
|
462
|
+
|
|
463
|
+
w := makewire()
|
|
464
|
+
client := secretsClient(w, map[string]any{
|
|
465
|
+
"auth": nil,
|
|
466
|
+
"apikey": "OPTKEY01",
|
|
467
|
+
"feature": secretsOpts(nil),
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
driveEntityOp(t, client, w)
|
|
471
|
+
|
|
472
|
+
// Nothing on the wire, even though the chain would have resolved:
|
|
473
|
+
// go's vendored struct treats a stored null as "no value"
|
|
474
|
+
// (fail-OPEN class), and makeOptions captures suppliedness before
|
|
475
|
+
// validate and restores the null after - the guard this pins.
|
|
476
|
+
call := w.api()[0]
|
|
477
|
+
if call.has {
|
|
478
|
+
t.Fatalf("auth nil must suppress the credential, got header %q", call.auth)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// The suppression survives option validation rather than being
|
|
482
|
+
// replaced by the optspec's default auth map.
|
|
483
|
+
opts := client.OptionsMap()
|
|
484
|
+
authval, authgiven := opts["auth"]
|
|
485
|
+
if !authgiven || nil != authval {
|
|
486
|
+
t.Fatalf("options.auth must stay a present null, got (%v, present=%v)", authval, authgiven)
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// customProvider is a sekreto.Provider built in code (the interface is
|
|
492
|
+
// structural: Lookup + Describe).
|
|
493
|
+
type customProvider struct {
|
|
494
|
+
lookup func(name string) (string, bool, error)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
func (p *customProvider) Lookup(name string) (string, bool, error) {
|
|
498
|
+
return p.lookup(name)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
func (p *customProvider) Describe() string { return "custom:test" }
|
|
502
|
+
|
|
503
|
+
// ---------------------------------------------------------------------
|
|
504
|
+
// The access-token exchange.
|
|
505
|
+
|
|
506
|
+
func TestSecretsExchange(t *testing.T) {
|
|
507
|
+
|
|
508
|
+
t.Run("the refresh token buys an access token, and a spent one is rebought once", func(t *testing.T) {
|
|
509
|
+
os.Setenv(envprefix+"REFRESH_TOKEN", "REFRESH01")
|
|
510
|
+
defer os.Unsetenv(envprefix + "REFRESH_TOKEN")
|
|
511
|
+
|
|
512
|
+
// First API call is refused, the retry succeeds.
|
|
513
|
+
w := makewire()
|
|
514
|
+
w.apistatus = []int{401, 200}
|
|
515
|
+
|
|
516
|
+
client := secretsClient(w, map[string]any{
|
|
517
|
+
"feature": secretsOpts(map[string]any{
|
|
518
|
+
"name": "refresh_token",
|
|
519
|
+
"exchange": map[string]any{"active": true},
|
|
520
|
+
}),
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
driveEntityOp(t, client, w)
|
|
524
|
+
|
|
525
|
+
if 2 != len(w.token()) {
|
|
526
|
+
t.Fatalf("expected the initial purchase plus one rebuy, got %d", len(w.token()))
|
|
527
|
+
}
|
|
528
|
+
if !strings.Contains(w.token()[0].body, "REFRESH01") {
|
|
529
|
+
t.Fatalf("the refresh token is sent in the request body, got %q", w.token()[0].body)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
api := w.api()
|
|
533
|
+
if 2 != len(api) {
|
|
534
|
+
t.Fatalf("expected the request to be retried exactly once, got %d calls", len(api))
|
|
535
|
+
}
|
|
536
|
+
credentialIs(t, api[0].auth, "ACCESS01")
|
|
537
|
+
// The retry must carry the NEW token, not the spent one.
|
|
538
|
+
credentialIs(t, api[1].auth, "ACCESS02")
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
t.Run("test mode buys nothing and needs no token endpoint", func(t *testing.T) {
|
|
542
|
+
os.Setenv(envprefix+"REFRESH_TOKEN", "REFRESH01")
|
|
543
|
+
defer os.Unsetenv(envprefix + "REFRESH_TOKEN")
|
|
544
|
+
|
|
545
|
+
w := makewire()
|
|
546
|
+
client := withSecrets(func(extend bool) *sdk.ProjectNameSDK {
|
|
547
|
+
opts := map[string]any{
|
|
548
|
+
"system": map[string]any{"fetch": w.fetch},
|
|
549
|
+
"feature": secretsOpts(map[string]any{
|
|
550
|
+
"name": "refresh_token",
|
|
551
|
+
"exchange": map[string]any{"active": true},
|
|
552
|
+
}),
|
|
553
|
+
}
|
|
554
|
+
if extend {
|
|
555
|
+
opts["extend"] = []any{feat.NewSecretsFeature()}
|
|
556
|
+
}
|
|
557
|
+
return sdk.TestSDK(nil, opts)
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
driveEntityOpUntil(t, client, "resolved the fake token", func() bool {
|
|
561
|
+
return "" != secretsFeatureOf(client).Credential()
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
if 0 != len(w.calls) {
|
|
565
|
+
t.Fatalf("test mode must not do IO, saw %d calls", len(w.calls))
|
|
566
|
+
}
|
|
567
|
+
// A deterministic placeholder, so offline suites need no
|
|
568
|
+
// configuration.
|
|
569
|
+
if v := secretsFeatureOf(client).Credential(); "test-access_token" != v {
|
|
570
|
+
t.Fatalf("expected the fake test token, got %q", v)
|
|
571
|
+
}
|
|
572
|
+
})
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// ---------------------------------------------------------------------
|
|
576
|
+
// Review-hardening pins (vendor-tag rollout, PR review): the exchange
|
|
577
|
+
// works with ORDINARY options (no custom transport), the token body is
|
|
578
|
+
// real JSON, an uncached miss retracts the credential, and a failed
|
|
579
|
+
// resolution recovers safely under concurrency.
|
|
580
|
+
|
|
581
|
+
// The exchange with NO system.fetch: the raw fallback transport carries
|
|
582
|
+
// the purchase, and the body is MARSHALLED - a refresh token full of
|
|
583
|
+
// JSON-hostile characters must arrive as that literal value. End to end
|
|
584
|
+
// over real HTTP (httptest), token purchase and API call alike.
|
|
585
|
+
func TestSecretsExchangeRawFetch(t *testing.T) {
|
|
586
|
+
tricky := "re\"fresh\\to\nken"
|
|
587
|
+
|
|
588
|
+
var mu sync.Mutex
|
|
589
|
+
gotrefresh := ""
|
|
590
|
+
gotauth := ""
|
|
591
|
+
|
|
592
|
+
srv := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
593
|
+
if strings.HasSuffix(req.URL.Path, "/auth/token") {
|
|
594
|
+
var body map[string]any
|
|
595
|
+
json.NewDecoder(req.Body).Decode(&body)
|
|
596
|
+
mu.Lock()
|
|
597
|
+
gotrefresh, _ = body["refresh_token"].(string)
|
|
598
|
+
mu.Unlock()
|
|
599
|
+
res.Header().Set("content-type", "application/json")
|
|
600
|
+
res.Write([]byte(`{"access_token": "RAWTOK01"}`))
|
|
601
|
+
return
|
|
602
|
+
}
|
|
603
|
+
mu.Lock()
|
|
604
|
+
gotauth = req.Header.Get("authorization")
|
|
605
|
+
mu.Unlock()
|
|
606
|
+
res.Header().Set("content-type", "application/json")
|
|
607
|
+
res.Write([]byte(`{}`))
|
|
608
|
+
}))
|
|
609
|
+
defer srv.Close()
|
|
610
|
+
|
|
611
|
+
// No system.fetch anywhere: the API call takes the SDK's default
|
|
612
|
+
// transport and the token purchase takes the feature's raw fallback.
|
|
613
|
+
client := withSecrets(func(extend bool) *sdk.ProjectNameSDK {
|
|
614
|
+
opts := map[string]any{
|
|
615
|
+
"base": srv.URL + "/api",
|
|
616
|
+
"feature": map[string]any{"secrets": map[string]any{
|
|
617
|
+
"active": true,
|
|
618
|
+
"name": "refresh_token",
|
|
619
|
+
"exchange": map[string]any{"active": true, "refresh": tricky},
|
|
620
|
+
}},
|
|
621
|
+
}
|
|
622
|
+
if extend {
|
|
623
|
+
opts["extend"] = []any{feat.NewSecretsFeature()}
|
|
624
|
+
}
|
|
625
|
+
return sdk.NewProjectNameSDK(opts)
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
driveEntityOpUntil(t, client, "reached the httptest server", func() bool {
|
|
629
|
+
mu.Lock()
|
|
630
|
+
defer mu.Unlock()
|
|
631
|
+
return "" != gotauth
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
mu.Lock()
|
|
635
|
+
defer mu.Unlock()
|
|
636
|
+
if tricky != gotrefresh {
|
|
637
|
+
t.Fatalf("the refresh token must arrive as its literal value "+
|
|
638
|
+
"(marshalled, not concatenated): want %q got %q", tricky, gotrefresh)
|
|
639
|
+
}
|
|
640
|
+
credentialIs(t, gotauth, "RAWTOK01")
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// With `cache: false`, a provider that answered once and then reports a
|
|
644
|
+
// MISS (a revoked secret) must RETRACT the credential the feature wrote -
|
|
645
|
+
// prepareAuth must stop transmitting it.
|
|
646
|
+
func TestSecretsUncachedMissRetracts(t *testing.T) {
|
|
647
|
+
var mu sync.Mutex
|
|
648
|
+
have := true
|
|
649
|
+
|
|
650
|
+
w := makewire()
|
|
651
|
+
client := secretsClient(w, map[string]any{
|
|
652
|
+
"feature": map[string]any{"secrets": map[string]any{
|
|
653
|
+
"active": true,
|
|
654
|
+
"cache": false,
|
|
655
|
+
"providers": []any{
|
|
656
|
+
&customProvider{
|
|
657
|
+
lookup: func(name string) (string, bool, error) {
|
|
658
|
+
mu.Lock()
|
|
659
|
+
defer mu.Unlock()
|
|
660
|
+
if have {
|
|
661
|
+
return "REVOCABLE01", true, nil
|
|
662
|
+
}
|
|
663
|
+
return "", false, nil
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
}},
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
driveEntityOp(t, client, w)
|
|
671
|
+
credentialIs(t, w.api()[0].auth, "REVOCABLE01")
|
|
672
|
+
|
|
673
|
+
mu.Lock()
|
|
674
|
+
have = false
|
|
675
|
+
mu.Unlock()
|
|
676
|
+
|
|
677
|
+
driveEntityOp(t, client, w)
|
|
678
|
+
last := w.api()[len(w.api())-1]
|
|
679
|
+
if last.has && "" != last.auth {
|
|
680
|
+
t.Fatalf("after the chain reports a miss, the retracted credential "+
|
|
681
|
+
"must not go out; the wire saw %q", last.auth)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// The RAW paths - Direct and Graphql - run no feature hooks at all, so
|
|
686
|
+
// for them the transport seam is the ONLY place resolution can happen.
|
|
687
|
+
// This is the pin that Direct gets the chain credential (and the same
|
|
688
|
+
// fail-closed refusal) without any entity pipeline involved.
|
|
689
|
+
func TestSecretsDirectPath(t *testing.T) {
|
|
690
|
+
os.Setenv(envprefix+"APIKEY", "DIRECTKEY01")
|
|
691
|
+
defer os.Unsetenv(envprefix + "APIKEY")
|
|
692
|
+
|
|
693
|
+
w := makewire()
|
|
694
|
+
client := secretsClient(w, map[string]any{
|
|
695
|
+
"allow": map[string]any{"op": "direct"},
|
|
696
|
+
"feature": secretsOpts(nil),
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
res, err := client.Direct(map[string]any{"path": "/direct-probe"})
|
|
700
|
+
if nil != err {
|
|
701
|
+
t.Fatalf("direct failed: %v", err)
|
|
702
|
+
}
|
|
703
|
+
if ok, _ := res["ok"].(bool); !ok {
|
|
704
|
+
t.Fatalf("direct refused: %v", res["err"])
|
|
705
|
+
}
|
|
706
|
+
if 1 != len(w.api()) {
|
|
707
|
+
t.Fatalf("expected one direct call on the wire, got %d", len(w.api()))
|
|
708
|
+
}
|
|
709
|
+
credentialIs(t, w.api()[0].auth, "DIRECTKEY01")
|
|
710
|
+
|
|
711
|
+
// And fail-closed holds for raw access too: a broken chain refuses
|
|
712
|
+
// the Direct call before anything reaches the wire.
|
|
713
|
+
broken := secretsClient(makewire(), map[string]any{
|
|
714
|
+
"allow": map[string]any{"op": "direct"},
|
|
715
|
+
"feature": map[string]any{"secrets": map[string]any{
|
|
716
|
+
"active": true,
|
|
717
|
+
"providers": []any{
|
|
718
|
+
&customProvider{
|
|
719
|
+
lookup: func(name string) (string, bool, error) {
|
|
720
|
+
return "", false, fmt.Errorf("vault unreachable")
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
}},
|
|
725
|
+
})
|
|
726
|
+
res, err = broken.Direct(map[string]any{"path": "/direct-probe"})
|
|
727
|
+
if nil != err {
|
|
728
|
+
t.Fatalf("direct must report the refusal in-band: %v", err)
|
|
729
|
+
}
|
|
730
|
+
if ok, _ := res["ok"].(bool); ok {
|
|
731
|
+
t.Fatal("a broken chain must refuse the raw path fail-closed")
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// A provider failure closes the transport gate; a later retry that
|
|
736
|
+
// SUCCEEDS reopens it and every waiting operation goes out with the FRESH
|
|
737
|
+
// credential - never the stale pre-failure header, and never nothing.
|
|
738
|
+
func TestSecretsGateRecovery(t *testing.T) {
|
|
739
|
+
var mu sync.Mutex
|
|
740
|
+
mode := "fail"
|
|
741
|
+
release := make(chan struct{})
|
|
742
|
+
|
|
743
|
+
w := makewire()
|
|
744
|
+
client := secretsClient(w, map[string]any{
|
|
745
|
+
"feature": map[string]any{"secrets": map[string]any{
|
|
746
|
+
"active": true,
|
|
747
|
+
"cache": false,
|
|
748
|
+
"providers": []any{
|
|
749
|
+
&customProvider{
|
|
750
|
+
lookup: func(name string) (string, bool, error) {
|
|
751
|
+
mu.Lock()
|
|
752
|
+
m := mode
|
|
753
|
+
mu.Unlock()
|
|
754
|
+
if "fail" == m {
|
|
755
|
+
return "", false, fmt.Errorf("vault unreachable")
|
|
756
|
+
}
|
|
757
|
+
if "slow" == m {
|
|
758
|
+
<-release
|
|
759
|
+
}
|
|
760
|
+
return "FRESH01", true, nil
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
}},
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
// 1. The failure closes the gate: nothing reaches the wire.
|
|
768
|
+
driveEntityOpUntil(t, client, "was refused by the gate", func() bool { return true })
|
|
769
|
+
if 0 != len(w.api()) {
|
|
770
|
+
t.Fatalf("a failed resolution must keep the wire silent, saw %d calls", len(w.api()))
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// 2. Recovery under concurrency: two operations race the slow retry;
|
|
774
|
+
// both must come out carrying the fresh credential.
|
|
775
|
+
mu.Lock()
|
|
776
|
+
mode = "slow"
|
|
777
|
+
mu.Unlock()
|
|
778
|
+
|
|
779
|
+
var wg sync.WaitGroup
|
|
780
|
+
for i := 0; i < 2; i++ {
|
|
781
|
+
wg.Add(1)
|
|
782
|
+
go func() {
|
|
783
|
+
defer wg.Done()
|
|
784
|
+
driveEntityOpUntil(t, client, "recovered", func() bool { return true })
|
|
785
|
+
}()
|
|
786
|
+
}
|
|
787
|
+
go func() {
|
|
788
|
+
time.Sleep(20 * time.Millisecond)
|
|
789
|
+
close(release)
|
|
790
|
+
}()
|
|
791
|
+
wg.Wait()
|
|
792
|
+
|
|
793
|
+
for _, call := range w.api() {
|
|
794
|
+
credentialIs(t, call.auth, "FRESH01")
|
|
795
|
+
}
|
|
796
|
+
if 0 == len(w.api()) {
|
|
797
|
+
t.Fatal("recovery must let the operations out")
|
|
798
|
+
}
|
|
799
|
+
}
|