@voxgig/sdkgen 4.10.0 → 4.11.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/package.json +1 -1
- package/project/.sdk/model/feature/secrets.aon +330 -0
- package/project/.sdk/model/target/c.aon +15 -2
- package/project/.sdk/model/target/cpp.aon +18 -0
- package/project/.sdk/model/target/lua.aon +17 -0
- package/project/.sdk/model/target/ocaml.aon +48 -5
- package/project/.sdk/model/target/zig.aon +30 -4
- package/project/.sdk/src/cmp/c/Config_c.ts +338 -1
- package/project/.sdk/src/cmp/c/Main_c.ts +57 -2
- package/project/.sdk/src/cmp/c/Package_c.ts +12 -2
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +324 -2
- package/project/.sdk/src/cmp/cpp/Gitignore_cpp.ts +2 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +62 -4
- package/project/.sdk/src/cmp/cpp/Package_cpp.ts +10 -0
- package/project/.sdk/src/cmp/lua/Config_lua.ts +88 -0
- package/project/.sdk/src/cmp/lua/Gitignore_lua.ts +4 -0
- package/project/.sdk/src/cmp/lua/Main_lua.ts +93 -3
- package/project/.sdk/src/cmp/lua/Package_lua.ts +60 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +10 -0
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +183 -5
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +160 -3
- package/project/.sdk/src/cmp/ocaml/Package_ocaml.ts +25 -1
- package/project/.sdk/src/cmp/zig/Config_zig.ts +130 -9
- package/project/.sdk/src/cmp/zig/Main_zig.ts +208 -4
- package/project/.sdk/{tm/zig/build.zig → src/cmp/zig/fragment/Build.fragment.zig} +7 -0
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +25 -0
- package/project/.sdk/{tm/zig/root.zig → src/cmp/zig/fragment/Root.fragment.zig} +9 -0
- package/project/.sdk/tm/c/Makefile +58 -9
- package/project/.sdk/tm/c/core/sdk.h +10 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/capability.c +133 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/capability.h +39 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/catalog.c +83 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/catalog.h +48 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/config.c +413 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/config.h +24 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/depend.c +264 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/depend.h +29 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/env.c +220 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/env.h +19 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/export.c +97 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/export.h +19 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/graph.c +237 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/graph.h +17 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/host.c +1494 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/host.h +135 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/order.c +288 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/order.h +18 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/point.c +141 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/point.h +80 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/ref.c +170 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/ref.h +52 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/resolve.c +98 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/resolve.h +18 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/types.c +150 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/types.h +104 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/value.c +649 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/value.h +135 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/version.c +151 -0
- package/project/.sdk/tm/c/feature/secrets/plugin/version.h +34 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/aws.c +281 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/azuresecrets.c +207 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/boru.c +164 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/clock.c +75 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/doppler.c +141 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/encode.c +144 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/gcpsecrets.c +177 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/hashicorp.c +239 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/httpjson.c +599 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/infisical.c +165 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/onepassword.c +194 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/proc.c +233 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/secretspec.c +125 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/sekretoplugins.h +125 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/sha256.c +270 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/sigv4.c +382 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/support.h +153 -0
- package/project/.sdk/tm/c/feature/secrets/plugins/tls.c +420 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/internal.h +86 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/json.c +721 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/providers.c +813 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/sekreto.c +944 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/sekreto.h +490 -0
- package/project/.sdk/tm/c/feature/secrets/sekreto/util.c +442 -0
- package/project/.sdk/tm/c/feature/secrets.c +797 -0
- package/project/.sdk/tm/c/feature/secrets.h +46 -0
- package/project/.sdk/tm/c/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/c/tests/feature/secrets/secrets_test.c +802 -0
- package/project/.sdk/tm/clojure/feature/secrets/sdk/feature/secrets.clj +38 -11
- package/project/.sdk/tm/clojure/test/sdk/test/feature/secrets.clj +40 -6
- package/project/.sdk/tm/cpp/Makefile +48 -4
- package/project/.sdk/tm/cpp/feature/secrets/plugin/capability.cpp +121 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/capability.hpp +41 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/catalog.cpp +60 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/catalog.hpp +66 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/config.cpp +376 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/config.hpp +29 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/depend.cpp +230 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/depend.hpp +31 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/env.cpp +205 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/env.hpp +25 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/export.cpp +84 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/export.hpp +24 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/graph.cpp +222 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/graph.hpp +21 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/host.cpp +1200 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/host.hpp +250 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/order.cpp +250 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/order.hpp +22 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/point.cpp +110 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/point.hpp +79 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/ref.cpp +136 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/ref.hpp +60 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/resolve.cpp +82 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/resolve.hpp +25 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/types.cpp +90 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/types.hpp +65 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/value.cpp +491 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/value.hpp +120 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/version.cpp +143 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugin/version.hpp +36 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Aws.cpp +248 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Aws.hpp +30 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Azuresecrets.cpp +149 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Azuresecrets.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Boru.cpp +117 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Boru.hpp +27 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Crypto.cpp +180 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Crypto.hpp +52 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Doppler.cpp +87 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Doppler.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Gcpsecrets.cpp +121 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Gcpsecrets.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Hashicorp.cpp +153 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Hashicorp.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Httpjson.cpp +666 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Httpjson.hpp +160 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Infisical.cpp +123 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Infisical.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Onepassword.cpp +125 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Onepassword.hpp +23 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Proc.cpp +223 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Proc.hpp +47 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Secretspec.cpp +104 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Secretspec.hpp +27 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Sigv4.cpp +242 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Sigv4.hpp +58 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Tls.cpp +260 -0
- package/project/.sdk/tm/cpp/feature/secrets/plugins/Tls.hpp +47 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Json.cpp +476 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Json.hpp +89 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Provider.cpp +312 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Provider.hpp +263 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Providers.cpp +354 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Providers.hpp +111 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Sekreto.cpp +613 -0
- package/project/.sdk/tm/cpp/feature/secrets/sekreto/Sekreto.hpp +227 -0
- package/project/.sdk/tm/cpp/feature/secrets.hpp +745 -0
- package/project/.sdk/tm/cpp/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/cpp/test/feature/secrets/secrets_test.cpp +808 -0
- package/project/.sdk/tm/csharp/feature/SecretsFeature.cs +34 -8
- package/project/.sdk/tm/csharp/test/feature/secrets/SecretsFeatureTest.cs +85 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/secrets.ex +17 -2
- package/project/.sdk/tm/elixir/test/feature/secrets/secrets_test.exs +41 -0
- package/project/.sdk/tm/go/feature/secrets_feature.go +32 -10
- package/project/.sdk/tm/go/test/feature/secrets/secrets_feature_test.go +116 -0
- package/project/.sdk/tm/java/feature/SecretsFeature.java +33 -8
- package/project/.sdk/tm/java/test/feature/secrets/SecretsFeatureTest.java +64 -0
- package/project/.sdk/tm/js/src/feature/secrets/SecretsFeature.js +34 -11
- package/project/.sdk/tm/js/test/feature/secrets/Secrets.test.js +73 -0
- package/project/.sdk/tm/kotlin/feature/SecretsFeature.kt +32 -7
- package/project/.sdk/tm/kotlin/test/feature/secrets/SecretsTest.kt +69 -0
- package/project/.sdk/tm/lua/Makefile +15 -4
- package/project/.sdk/tm/lua/feature/secrets/native/sekretonet.c +806 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/capability.lua +135 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/catalog.lua +63 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/config.lua +361 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/depend.lua +232 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/env.lua +168 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/export.lua +66 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/graph.lua +194 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/host.lua +1190 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/json.lua +169 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/order.lua +207 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/point.lua +106 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/ref.lua +115 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/resolve.lua +78 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/types.lua +275 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin/version.lua +131 -0
- package/project/.sdk/tm/lua/feature/secrets/plugin.lua +76 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/addr.lua +124 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/err.lua +47 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/name.lua +315 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/aws.lua +228 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/azuresecrets.lua +137 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/boru.lua +124 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/crypto.lua +212 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/doppler.lua +89 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/gcpsecrets.lua +129 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/hashicorp.lua +154 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/httpjson.lua +248 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/infisical.lua +113 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/json.lua +449 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/net.lua +198 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/onepassword.lua +126 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/secretspec.lua +93 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/sigv4.lua +269 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/plugins/support.lua +146 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto/providers.lua +346 -0
- package/project/.sdk/tm/lua/feature/secrets/sekreto.lua +479 -0
- package/project/.sdk/tm/lua/feature/secrets_feature.lua +690 -0
- package/project/.sdk/tm/lua/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/lua/test/extend_test.lua +105 -0
- package/project/.sdk/tm/lua/test/feature/secrets/secrets_feature_test.lua +862 -0
- package/project/.sdk/tm/ocaml/Makefile +52 -8
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/capability.ml +106 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/catalog.ml +43 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/config.ml +349 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/defs.ml +143 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/depend.ml +217 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/env.ml +197 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/export.ml +89 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/graph.ml +227 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/host.ml +1112 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/order.ml +202 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/point.ml +138 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/ref.ml +126 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/resolve.ml +62 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/types.ml +97 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/value.ml +387 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugin/version.ml +139 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/aws.ml +171 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/azuresecrets.ml +116 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/boru.ml +95 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/crypto.ml +149 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/doppler.ml +61 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/gcpsecrets.ml +93 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/hashicorp.ml +120 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/http.ml +421 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/httpjson.ml +66 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/infisical.ml +85 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/onepassword.ml +112 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/runcmd.ml +97 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/secretspec.ml +68 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/sigv4.ml +259 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/tls.ml +44 -0
- package/project/.sdk/tm/ocaml/feature/secrets/plugins/tls_stubs.c +358 -0
- package/project/.sdk/tm/ocaml/feature/secrets/sekreto/json.ml +376 -0
- package/project/.sdk/tm/ocaml/feature/secrets/sekreto/provider.ml +647 -0
- package/project/.sdk/tm/ocaml/feature/secrets/sekreto/secret.ml +299 -0
- package/project/.sdk/tm/ocaml/feature/secrets/sekreto/sekreto.ml +347 -0
- package/project/.sdk/tm/ocaml/feature/secrets_feature.ml +453 -0
- package/project/.sdk/tm/ocaml/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +670 -0
- package/project/.sdk/tm/perl/feature/secrets_feature.pm +33 -12
- package/project/.sdk/tm/perl/t/feature/secrets/secrets.t +47 -0
- package/project/.sdk/tm/php/feature/SecretsFeature.php +30 -6
- package/project/.sdk/tm/php/test/feature/secrets/SecretsTest.php +98 -0
- package/project/.sdk/tm/py/pkg/feature/secrets_feature.py +28 -6
- package/project/.sdk/tm/py/test/feature/secrets/test_secrets.py +114 -0
- package/project/.sdk/tm/rb/feature/secrets_feature.rb +28 -6
- package/project/.sdk/tm/rb/test/feature/secrets/secrets_feature_test.rb +93 -0
- package/project/.sdk/tm/rust/feature/secrets.rs +33 -10
- package/project/.sdk/tm/rust/tests/feature/secrets/main.rs +96 -0
- package/project/.sdk/tm/scala/feature/SecretsFeature.scala +29 -7
- package/project/.sdk/tm/scala/sdktest/feature/secrets/SecretsTestMain.scala +71 -0
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +35 -12
- package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +73 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/capability.zig +137 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/catalog.zig +90 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/config.zig +346 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/depend.zig +244 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/env.zig +201 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/export.zig +83 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/graph.zig +212 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/host.zig +1224 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/inst.zig +126 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/order.zig +228 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/plugin.zig +34 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/point.zig +166 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/ref.zig +147 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/resolve.zig +74 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/types.zig +140 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/value.zig +662 -0
- package/project/.sdk/tm/zig/feature/secrets/plugin/version.zig +139 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/aws.zig +294 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/azuresecrets.zig +222 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/boru.zig +188 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/doppler.zig +135 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/gcpsecrets.zig +170 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/hashicorp.zig +242 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/httpjson.zig +535 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/infisical.zig +167 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/onepassword.zig +202 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/secretspec.zig +160 -0
- package/project/.sdk/tm/zig/feature/secrets/plugins/sigv4.zig +336 -0
- package/project/.sdk/tm/zig/feature/secrets/sekreto/addr.zig +148 -0
- package/project/.sdk/tm/zig/feature/secrets/sekreto/builtins.zig +259 -0
- package/project/.sdk/tm/zig/feature/secrets/sekreto/provider.zig +440 -0
- package/project/.sdk/tm/zig/feature/secrets/sekreto/sekreto.zig +944 -0
- package/project/.sdk/tm/zig/feature/secrets.zig +854 -0
- package/project/.sdk/tm/zig/src/feature/secrets/.gitkeep +0 -0
- package/project/.sdk/tm/zig/test/feature/secrets/secrets_test.zig +891 -0
- package/project/sdkgen-package.json +1 -1
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
-- ProjectName SDK secrets feature test
|
|
2
|
+
--
|
|
3
|
+
-- Behavioural tests for the secrets feature (vendored @voxgig/sekreto) -
|
|
4
|
+
-- the lua port of tm/go/test/feature/secrets/secrets_feature_test.go.
|
|
5
|
+
--
|
|
6
|
+
-- The contract under test: the `apikey` OPTION keeps its exact old meaning
|
|
7
|
+
-- and always wins, because the feature places it FIRST in the provider
|
|
8
|
+
-- chain (a `memory` store named `options`) - explicit-beats-lookup falls
|
|
9
|
+
-- out of sekreto's first-hit rule rather than from special-case logic.
|
|
10
|
+
-- With the feature inactive nothing changes at all. With it active and the
|
|
11
|
+
-- option unset, the chain (a memory store, a custom provider, a vault)
|
|
12
|
+
-- supplies the credential instead.
|
|
13
|
+
--
|
|
14
|
+
-- This file lives in the test `feature/` container on purpose: `target
|
|
15
|
+
-- add` trims it, along with the feature source and the vendored library,
|
|
16
|
+
-- for a project whose model does not select `secrets`.
|
|
17
|
+
--
|
|
18
|
+
-- HOW EVERY CASE IS BUILT, and why:
|
|
19
|
+
--
|
|
20
|
+
-- * a LIVE client (`sdk.new`), so `system.fetch` really is the transport
|
|
21
|
+
-- and the credential is asserted ON THE WIRE - an options-level
|
|
22
|
+
-- assertion passes for a port that never consults the value;
|
|
23
|
+
-- * the feature handed in through `extend` unless the generated config
|
|
24
|
+
-- already installed it, so the suite holds in any generated tree;
|
|
25
|
+
-- * a refusal asserted on sekreto's OWN message, never on "some error";
|
|
26
|
+
-- * and a CONTROL leg beside every refusal: the same construction with a
|
|
27
|
+
-- working provider must reach the same transport EXACTLY ONCE, so a
|
|
28
|
+
-- zero on the refused side means REFUSED rather than UNWIRED.
|
|
29
|
+
--
|
|
30
|
+
-- Two things Lua cannot do that the other ports' suites do. It cannot set
|
|
31
|
+
-- an environment variable, so the chain's stores are `memory`, `file` and
|
|
32
|
+
-- custom providers rather than `env`; and it cannot spell `auth = nil` in
|
|
33
|
+
-- an options table (a table stores no nil, and validate then fills the
|
|
34
|
+
-- default), so the suppression is exercised in the one form that IS
|
|
35
|
+
-- expressible - clearing `client.options.auth` after construction - and
|
|
36
|
+
-- pinned there.
|
|
37
|
+
|
|
38
|
+
local sdk = require("project-name-_sdk")
|
|
39
|
+
local SecretsFeature = require("feature.secrets_feature")
|
|
40
|
+
local sekreto = require("feature.secrets.sekreto")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
-- ---------------------------------------------------------------------
|
|
44
|
+
-- The recording transport: system.fetch for a LIVE client, scripting one
|
|
45
|
+
-- status per API call (the last repeating) and a token endpoint for the
|
|
46
|
+
-- exchange tests.
|
|
47
|
+
|
|
48
|
+
local function make_wire(script)
|
|
49
|
+
script = script or {}
|
|
50
|
+
local w = {
|
|
51
|
+
calls = {},
|
|
52
|
+
apistatus = script.apistatus or { 200 },
|
|
53
|
+
tokens = script.tokens or { "ACCESS01", "ACCESS02", "ACCESS03" },
|
|
54
|
+
tokenpath = "auth/token",
|
|
55
|
+
respfield = script.respfield or "access_token",
|
|
56
|
+
issued = 0,
|
|
57
|
+
apicalls = 0,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
local function is_token(url)
|
|
61
|
+
local suffix = "/" .. w.tokenpath
|
|
62
|
+
return url:sub(-#suffix) == suffix
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
w.fetch = function(url, fetchdef)
|
|
66
|
+
local headers = type(fetchdef) == "table" and fetchdef.headers or {}
|
|
67
|
+
local auth = headers["authorization"]
|
|
68
|
+
w.calls[#w.calls + 1] = {
|
|
69
|
+
url = url, auth = auth, has = auth ~= nil,
|
|
70
|
+
body = fetchdef.body, method = fetchdef.method,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if is_token(url) then
|
|
74
|
+
local token = w.tokens[math.min(w.issued + 1, #w.tokens)]
|
|
75
|
+
w.issued = w.issued + 1
|
|
76
|
+
local payload = { [w.respfield] = token }
|
|
77
|
+
return {
|
|
78
|
+
status = 200, statusText = "OK", headers = {},
|
|
79
|
+
json = function() return payload end,
|
|
80
|
+
}, nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
local status = w.apistatus[math.min(w.apicalls + 1, #w.apistatus)]
|
|
84
|
+
w.apicalls = w.apicalls + 1
|
|
85
|
+
local payload = { ok = status < 400 }
|
|
86
|
+
return {
|
|
87
|
+
status = status, statusText = "X", headers = {},
|
|
88
|
+
json = function() return payload end,
|
|
89
|
+
}, nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
-- The recorded calls that did NOT go to the token endpoint.
|
|
93
|
+
function w.api()
|
|
94
|
+
local out = {}
|
|
95
|
+
for _, c in ipairs(w.calls) do
|
|
96
|
+
if not is_token(c.url) then out[#out + 1] = c end
|
|
97
|
+
end
|
|
98
|
+
return out
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
function w.token()
|
|
102
|
+
local out = {}
|
|
103
|
+
for _, c in ipairs(w.calls) do
|
|
104
|
+
if is_token(c.url) then out[#out + 1] = c end
|
|
105
|
+
end
|
|
106
|
+
return out
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
return w
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
-- ---------------------------------------------------------------------
|
|
114
|
+
-- Support.
|
|
115
|
+
|
|
116
|
+
-- The Authorization header carries the SPEC's credential prefix, which a
|
|
117
|
+
-- TEMPLATE cannot know - so assert on the CREDENTIAL and let the prefix be
|
|
118
|
+
-- whatever this SDK's API declares.
|
|
119
|
+
local function assert_credential(header, token)
|
|
120
|
+
local ok = header == token
|
|
121
|
+
or (type(header) == "string" and header:sub(-(#token + 1)) == " " .. token)
|
|
122
|
+
assert.is_true(ok, "expected the authorization header to carry " .. token ..
|
|
123
|
+
", got: " .. tostring(header))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
local function secrets_feature_of(client)
|
|
128
|
+
for _, f in ipairs(client.features) do
|
|
129
|
+
if f.name == "secrets" then return f end
|
|
130
|
+
end
|
|
131
|
+
return nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
-- A live provider, as a caller would hand one in: a table with a callable
|
|
136
|
+
-- `lookup` (sekreto's duck-typed shape) and a `describe`.
|
|
137
|
+
local function custom_provider(lookup, label)
|
|
138
|
+
return {
|
|
139
|
+
lookup = lookup,
|
|
140
|
+
describe = function() return label or "custom" end,
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
local function working_provider(value)
|
|
145
|
+
return custom_provider(function(_name) return value end, "working")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
local function broken_provider(message)
|
|
149
|
+
return custom_provider(function(_name)
|
|
150
|
+
error(sekreto.SekretoError(message), 0)
|
|
151
|
+
end, "broken")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
-- A LIVE client carrying the secrets feature via the `extend` seam, wired
|
|
156
|
+
-- to the recording transport. The feature is adopted via `extend` ONLY
|
|
157
|
+
-- when the generated config did not already install it - when this SDK
|
|
158
|
+
-- was generated with `secrets` model-active, the ordinary factory path
|
|
159
|
+
-- builds the instance, and adding a second via extend would DOUBLE the
|
|
160
|
+
-- feature: two transport wraps, two resolutions, and a token purchase the
|
|
161
|
+
-- assertions cannot account for.
|
|
162
|
+
local function secrets_client(w, sdkopts)
|
|
163
|
+
local function build(extend)
|
|
164
|
+
local opts = {
|
|
165
|
+
base = "http://secrets.test/api",
|
|
166
|
+
system = { fetch = w.fetch },
|
|
167
|
+
}
|
|
168
|
+
for k, v in pairs(sdkopts or {}) do opts[k] = v end
|
|
169
|
+
if extend then
|
|
170
|
+
opts.extend = { SecretsFeature.new() }
|
|
171
|
+
end
|
|
172
|
+
return sdk.new(opts)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
local client = build(false)
|
|
176
|
+
if secrets_feature_of(client) == nil then
|
|
177
|
+
client = build(true)
|
|
178
|
+
end
|
|
179
|
+
return client
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
-- Feature options with a chain: the given providers, or a memory store
|
|
184
|
+
-- holding `value` under the default secret name.
|
|
185
|
+
local function chain(providers, extra)
|
|
186
|
+
local fopts = { active = true, providers = providers }
|
|
187
|
+
for k, v in pairs(extra or {}) do fopts[k] = v end
|
|
188
|
+
return { secrets = fopts }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
local function memory(value, name)
|
|
192
|
+
return { kind = "memory", name = name, values = { APIKEY = value } }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
-- The entity accessors this SDK generated, found from the client's own
|
|
197
|
+
-- config: this file is a TEMPLATE and no project's entity names are known
|
|
198
|
+
-- here. Accessors are PascalCase methods on the SDK class.
|
|
199
|
+
local function entity_accessors(client)
|
|
200
|
+
local class = getmetatable(client).__index
|
|
201
|
+
local out = {}
|
|
202
|
+
for name, _ in pairs(client:options_map().entity or {}) do
|
|
203
|
+
local flat = name:gsub("_", ""):lower()
|
|
204
|
+
for k, v in pairs(class) do
|
|
205
|
+
if type(v) == "function" and k:match("^%u") and k:lower() == flat then
|
|
206
|
+
out[#out + 1] = k
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
table.sort(out)
|
|
211
|
+
return out
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
-- Perform real entity operations - which is what runs the request
|
|
216
|
+
-- pipeline - until `stop` reports the observable state a test is waiting
|
|
217
|
+
-- for. Each op's own outcome is irrelevant (no seeded data, a scripted
|
|
218
|
+
-- response); an op the API does not define fails BEFORE the transport,
|
|
219
|
+
-- which is why several may need driving. Returns the last (res, err).
|
|
220
|
+
local function drive_entity_op_until(client, what, stop)
|
|
221
|
+
local last_res, last_err = nil, nil
|
|
222
|
+
for _, accessor in ipairs(entity_accessors(client)) do
|
|
223
|
+
local ent = client[accessor](client)
|
|
224
|
+
for _, opname in ipairs({ "list", "load" }) do
|
|
225
|
+
if type(ent[opname]) == "function" then
|
|
226
|
+
last_res, last_err = ent[opname](ent, { id = "id01" })
|
|
227
|
+
if stop() then return last_res, last_err end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
error("no entity operation " .. what .. " - nothing to assert on", 0)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
-- Drive ops until one request reached the recorder.
|
|
235
|
+
local function drive_entity_op(client, w)
|
|
236
|
+
local before = #w.api()
|
|
237
|
+
return drive_entity_op_until(client, "reached the transport",
|
|
238
|
+
function() return before < #w.api() end)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
-- Drive ops until the chain was consulted, and hand back the op's error.
|
|
242
|
+
local function drive_refusal(client, asked)
|
|
243
|
+
local _, err = drive_entity_op_until(client, "consulted the chain", asked)
|
|
244
|
+
return err
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
-- ---------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
describe("secrets", function()
|
|
251
|
+
|
|
252
|
+
-- The feature-inactive baseline: bit-identical behaviour.
|
|
253
|
+
describe("inactive", function()
|
|
254
|
+
|
|
255
|
+
it("apikey option behaves exactly as before", function()
|
|
256
|
+
local client = sdk.test(nil, { apikey = "OPTKEY01" })
|
|
257
|
+
local fetchdef, err = client:prepare({ path = "/" })
|
|
258
|
+
assert.is_nil(err)
|
|
259
|
+
assert_credential(fetchdef.headers["authorization"], "OPTKEY01")
|
|
260
|
+
assert.is_nil(secrets_feature_of(client),
|
|
261
|
+
"no model activation, no extend: the feature must not be installed")
|
|
262
|
+
end)
|
|
263
|
+
|
|
264
|
+
it("no apikey means no authorization header", function()
|
|
265
|
+
local client = sdk.test(nil, nil)
|
|
266
|
+
local fetchdef, err = client:prepare({ path = "/" })
|
|
267
|
+
assert.is_nil(err)
|
|
268
|
+
assert.is_nil(fetchdef.headers["authorization"])
|
|
269
|
+
end)
|
|
270
|
+
|
|
271
|
+
it("an inactive feature wraps nothing and resolves nothing", function()
|
|
272
|
+
local w = make_wire()
|
|
273
|
+
local client = sdk.new({
|
|
274
|
+
base = "http://secrets.test/api",
|
|
275
|
+
system = { fetch = w.fetch },
|
|
276
|
+
feature = { secrets = { active = false, providers = { memory("CHAIN00") } } },
|
|
277
|
+
extend = { SecretsFeature.new() },
|
|
278
|
+
})
|
|
279
|
+
client:direct({ path = "/probe" })
|
|
280
|
+
assert.are.equal(1, #w.api())
|
|
281
|
+
assert.is_false(w.api()[1].has, "an inactive feature must not resolve a credential")
|
|
282
|
+
end)
|
|
283
|
+
end)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
-- Active: the provider chain, driven through real entity operations.
|
|
287
|
+
describe("chain", function()
|
|
288
|
+
|
|
289
|
+
it("apikey option still wins over the chain", function()
|
|
290
|
+
local w = make_wire()
|
|
291
|
+
local client = secrets_client(w, {
|
|
292
|
+
apikey = "OPTKEY01",
|
|
293
|
+
feature = chain({ memory("CHAIN01") }),
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
drive_entity_op(client, w)
|
|
297
|
+
assert_credential(w.api()[1].auth, "OPTKEY01")
|
|
298
|
+
|
|
299
|
+
-- The explicit option is a real store, not a special case: a
|
|
300
|
+
-- directed read names it like any other.
|
|
301
|
+
local sf = secrets_feature_of(client)
|
|
302
|
+
assert.is_not_nil(sf, "the extend seam did not install the feature")
|
|
303
|
+
assert.are.equal("OPTKEY01", sf:sekreto():getfrom("options", "apikey"))
|
|
304
|
+
end)
|
|
305
|
+
|
|
306
|
+
it("an omitted apikey defers to the chain at the transport seam", function()
|
|
307
|
+
local w = make_wire()
|
|
308
|
+
local client = secrets_client(w, { feature = chain({ memory("CHAIN02") }) })
|
|
309
|
+
|
|
310
|
+
-- Before any op, nothing has been resolved.
|
|
311
|
+
assert.is_nil(secrets_feature_of(client):credential())
|
|
312
|
+
|
|
313
|
+
drive_entity_op(client, w)
|
|
314
|
+
|
|
315
|
+
-- Resolution happens AT THE TRANSPORT - the one seam every wire
|
|
316
|
+
-- path crosses - so the credential is on the wire, not merely
|
|
317
|
+
-- resolved. The feature holds it; the options map is never written.
|
|
318
|
+
assert_credential(w.api()[1].auth, "CHAIN02")
|
|
319
|
+
assert.are.equal("CHAIN02", secrets_feature_of(client):credential())
|
|
320
|
+
assert.are.equal("", client:options_map().apikey,
|
|
321
|
+
"the options map must stay unwritten")
|
|
322
|
+
end)
|
|
323
|
+
|
|
324
|
+
it("custom provider objects are accepted verbatim", function()
|
|
325
|
+
local asked = {}
|
|
326
|
+
local w = make_wire()
|
|
327
|
+
local client = secrets_client(w, {
|
|
328
|
+
feature = chain({
|
|
329
|
+
custom_provider(function(name)
|
|
330
|
+
asked[#asked + 1] = name
|
|
331
|
+
return "CUSTOM01"
|
|
332
|
+
end),
|
|
333
|
+
}),
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
drive_entity_op(client, w)
|
|
337
|
+
assert_credential(w.api()[1].auth, "CUSTOM01")
|
|
338
|
+
assert.are.equal("apikey", asked[1])
|
|
339
|
+
end)
|
|
340
|
+
|
|
341
|
+
it("a miss everywhere leaves the header off", function()
|
|
342
|
+
local w = make_wire()
|
|
343
|
+
local client = secrets_client(w, {
|
|
344
|
+
feature = chain({ { kind = "memory", values = {} } }),
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
drive_entity_op(client, w)
|
|
348
|
+
assert.is_false(w.api()[1].has,
|
|
349
|
+
"a chain MISS must fall through to an unauthenticated request, got " ..
|
|
350
|
+
tostring(w.api()[1].auth))
|
|
351
|
+
end)
|
|
352
|
+
|
|
353
|
+
it("a miss falls through to the next provider", function()
|
|
354
|
+
local asked = false
|
|
355
|
+
local w = make_wire()
|
|
356
|
+
local client = secrets_client(w, {
|
|
357
|
+
feature = chain({
|
|
358
|
+
custom_provider(function(_name) asked = true; return nil end, "empty"),
|
|
359
|
+
memory("CHAIN04"),
|
|
360
|
+
}),
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
drive_entity_op(client, w)
|
|
364
|
+
assert.is_true(asked, "the first provider was never consulted")
|
|
365
|
+
assert_credential(w.api()[1].auth, "CHAIN04")
|
|
366
|
+
end)
|
|
367
|
+
|
|
368
|
+
it("a provider ERROR fails the op and nothing reaches the wire", function()
|
|
369
|
+
local asked = false
|
|
370
|
+
local w = make_wire()
|
|
371
|
+
local client = secrets_client(w, {
|
|
372
|
+
feature = chain({
|
|
373
|
+
custom_provider(function(_name)
|
|
374
|
+
asked = true
|
|
375
|
+
error(sekreto.SekretoError("sekreto: vault unreachable"), 0)
|
|
376
|
+
end, "broken"),
|
|
377
|
+
}),
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
local err = drive_refusal(client, function() return asked end)
|
|
381
|
+
|
|
382
|
+
-- The entity pipeline turns the refusal into the SDK error carrying
|
|
383
|
+
-- the provider's OWN message - never a silent unauthenticated send.
|
|
384
|
+
assert.is_table(err, "the entity path did not surface the refusal")
|
|
385
|
+
assert.is_truthy(tostring(err):find("sekreto: vault unreachable", 1, true),
|
|
386
|
+
"the refusal must carry the provider's own message, got: " .. tostring(err))
|
|
387
|
+
assert.are.equal(0, #w.api(),
|
|
388
|
+
"a broken vault must never yield a request: " .. #w.api() .. " calls went out")
|
|
389
|
+
|
|
390
|
+
-- CONTROL: the same construction with a working provider reaches
|
|
391
|
+
-- the same transport exactly once, so the zero above is REFUSED,
|
|
392
|
+
-- not UNWIRED.
|
|
393
|
+
local control = make_wire()
|
|
394
|
+
local ok_client = secrets_client(control, { feature = chain({ working_provider("CTRL01") }) })
|
|
395
|
+
drive_entity_op(ok_client, control)
|
|
396
|
+
assert.are.equal(1, #control.api(), "the control operation did not reach system.fetch exactly once")
|
|
397
|
+
assert_credential(control.api()[1].auth, "CTRL01")
|
|
398
|
+
end)
|
|
399
|
+
|
|
400
|
+
it("an error fails the op even though a later provider has the secret", function()
|
|
401
|
+
local w = make_wire()
|
|
402
|
+
local client = secrets_client(w, {
|
|
403
|
+
feature = chain({
|
|
404
|
+
broken_provider("sekreto: vault unreachable"),
|
|
405
|
+
memory("CHAIN05"),
|
|
406
|
+
}),
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
local res = client:direct({ path = "/probe" })
|
|
410
|
+
assert.is_false(res.ok, "an ERROR must not fall through to the next provider")
|
|
411
|
+
assert.is_truthy(tostring(res.err):find("sekreto: vault unreachable", 1, true),
|
|
412
|
+
"expected the provider's own message, got: " .. tostring(res.err))
|
|
413
|
+
assert.are.equal(0, #w.api())
|
|
414
|
+
end)
|
|
415
|
+
|
|
416
|
+
it("a broken store does not poison the client after it recovers", function()
|
|
417
|
+
local broken = true
|
|
418
|
+
local w = make_wire()
|
|
419
|
+
local client = secrets_client(w, {
|
|
420
|
+
feature = chain({
|
|
421
|
+
custom_provider(function(_name)
|
|
422
|
+
if broken then error(sekreto.SekretoError("sekreto: vault unreachable"), 0) end
|
|
423
|
+
return "RECOVERED01"
|
|
424
|
+
end, "flaky"),
|
|
425
|
+
}),
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
local res = client:direct({ path = "/probe" })
|
|
429
|
+
assert.is_false(res.ok)
|
|
430
|
+
assert.are.equal(0, #w.api())
|
|
431
|
+
|
|
432
|
+
broken = false
|
|
433
|
+
res = client:direct({ path = "/probe" })
|
|
434
|
+
assert.is_true(res.ok, "the recovered chain must serve the next request: " .. tostring(res.err))
|
|
435
|
+
assert.are.equal(1, #w.api())
|
|
436
|
+
assert_credential(w.api()[1].auth, "RECOVERED01")
|
|
437
|
+
end)
|
|
438
|
+
|
|
439
|
+
it("the file built-in reads a mounted secret directory", function()
|
|
440
|
+
local dir = os.tmpname()
|
|
441
|
+
os.remove(dir)
|
|
442
|
+
local made = os.execute("mkdir -p '" .. dir .. "'")
|
|
443
|
+
assert.is_true(made == true or made == 0, "could not make " .. dir)
|
|
444
|
+
local fh = assert(io.open(dir .. "/APIKEY", "wb"))
|
|
445
|
+
fh:write("FILEKEY01\n")
|
|
446
|
+
fh:close()
|
|
447
|
+
|
|
448
|
+
local w = make_wire()
|
|
449
|
+
local client = secrets_client(w, { feature = chain({ { kind = "file", dir = dir } }) })
|
|
450
|
+
drive_entity_op(client, w)
|
|
451
|
+
assert_credential(w.api()[1].auth, "FILEKEY01")
|
|
452
|
+
|
|
453
|
+
os.remove(dir .. "/APIKEY")
|
|
454
|
+
os.remove(dir)
|
|
455
|
+
end)
|
|
456
|
+
end)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
-- The raw paths run no feature hooks at all; resolution at the transport
|
|
460
|
+
-- is what gives them the same credential - and the same refusal.
|
|
461
|
+
describe("raw paths", function()
|
|
462
|
+
|
|
463
|
+
it("direct() carries the chain-resolved credential", function()
|
|
464
|
+
local w = make_wire()
|
|
465
|
+
local client = secrets_client(w, { feature = chain({ memory("RAWKEY01") }) })
|
|
466
|
+
local res = client:direct({ path = "/probe" })
|
|
467
|
+
assert.is_true(res.ok)
|
|
468
|
+
assert.are.equal(1, #w.api())
|
|
469
|
+
assert_credential(w.api()[1].auth, "RAWKEY01")
|
|
470
|
+
end)
|
|
471
|
+
|
|
472
|
+
it("graphql() carries the chain-resolved credential", function()
|
|
473
|
+
local w = make_wire()
|
|
474
|
+
local client = secrets_client(w, { feature = chain({ memory("RAWKEY02") }) })
|
|
475
|
+
local res = client:graphql("{ thing }", {})
|
|
476
|
+
assert.is_true(res.ok, tostring(res.err))
|
|
477
|
+
assert.are.equal(1, #w.api())
|
|
478
|
+
assert_credential(w.api()[1].auth, "RAWKEY02")
|
|
479
|
+
assert.are.equal("POST", w.api()[1].method)
|
|
480
|
+
end)
|
|
481
|
+
|
|
482
|
+
it("direct() is refused on a provider error, with the provider's message", function()
|
|
483
|
+
local w = make_wire()
|
|
484
|
+
local client = secrets_client(w, {
|
|
485
|
+
feature = chain({ broken_provider("sekreto: vault unreachable") }),
|
|
486
|
+
})
|
|
487
|
+
local res = client:direct({ path = "/probe" })
|
|
488
|
+
assert.is_false(res.ok)
|
|
489
|
+
assert.is_truthy(tostring(res.err):find("sekreto: vault unreachable", 1, true),
|
|
490
|
+
"got: " .. tostring(res.err))
|
|
491
|
+
assert.are.equal(0, #w.api())
|
|
492
|
+
|
|
493
|
+
-- CONTROL, through the same raw path.
|
|
494
|
+
local control = make_wire()
|
|
495
|
+
local ok_client = secrets_client(control, { feature = chain({ working_provider("RAWCTRL01") }) })
|
|
496
|
+
local ok_res = ok_client:direct({ path = "/probe" })
|
|
497
|
+
assert.is_true(ok_res.ok)
|
|
498
|
+
assert.are.equal(1, #control.api())
|
|
499
|
+
assert_credential(control.api()[1].auth, "RAWCTRL01")
|
|
500
|
+
end)
|
|
501
|
+
|
|
502
|
+
it("graphql() is refused on a provider error, with the provider's message", function()
|
|
503
|
+
local w = make_wire()
|
|
504
|
+
local client = secrets_client(w, {
|
|
505
|
+
feature = chain({ broken_provider("sekreto: vault unreachable") }),
|
|
506
|
+
})
|
|
507
|
+
local res = client:graphql("{ thing }", {})
|
|
508
|
+
assert.is_false(res.ok)
|
|
509
|
+
assert.is_truthy(tostring(res.err):find("sekreto: vault unreachable", 1, true),
|
|
510
|
+
"got: " .. tostring(res.err))
|
|
511
|
+
assert.are.equal(0, #w.api())
|
|
512
|
+
|
|
513
|
+
local control = make_wire()
|
|
514
|
+
local ok_client = secrets_client(control, { feature = chain({ working_provider("RAWCTRL02") }) })
|
|
515
|
+
local ok_res = ok_client:graphql("{ thing }", {})
|
|
516
|
+
assert.is_true(ok_res.ok)
|
|
517
|
+
assert.are.equal(1, #control.api())
|
|
518
|
+
assert_credential(control.api()[1].auth, "RAWCTRL02")
|
|
519
|
+
end)
|
|
520
|
+
end)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
-- Construction failure is a refusal, never a shortened chain.
|
|
524
|
+
describe("init failure gate", function()
|
|
525
|
+
|
|
526
|
+
local NOTAPROVIDER = "not a provider or a provider spec"
|
|
527
|
+
|
|
528
|
+
local function refused_with(providers, fragment)
|
|
529
|
+
local w = make_wire()
|
|
530
|
+
local client = secrets_client(w, { feature = chain(providers) })
|
|
531
|
+
|
|
532
|
+
local sf = secrets_feature_of(client)
|
|
533
|
+
assert.is_not_nil(sf:init_error(), "construction must record the failure")
|
|
534
|
+
|
|
535
|
+
local res = client:direct({ path = "/probe" })
|
|
536
|
+
assert.is_false(res.ok, "a client whose chain could not be built must refuse")
|
|
537
|
+
assert.is_truthy(tostring(res.err):find(fragment, 1, true),
|
|
538
|
+
"expected sekreto's own message (" .. fragment .. "), got: " .. tostring(res.err))
|
|
539
|
+
assert.are.equal(0, #w.api(), "nothing may reach the wire")
|
|
540
|
+
|
|
541
|
+
local err = drive_refusal(client, function() return true end)
|
|
542
|
+
assert.is_table(err, "the entity path must refuse too")
|
|
543
|
+
assert.is_truthy(tostring(err):find(fragment, 1, true), "got: " .. tostring(err))
|
|
544
|
+
assert.are.equal(0, #w.api())
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
it("a bare kind name in providers is refused, not dropped", function()
|
|
548
|
+
-- CONTROL FIRST, so the zero below is known to be observable.
|
|
549
|
+
local control = make_wire()
|
|
550
|
+
local ok_client = secrets_client(control, { feature = chain({ working_provider("INITKEY01") }) })
|
|
551
|
+
drive_entity_op(ok_client, control)
|
|
552
|
+
assert.are.equal(1, #control.api())
|
|
553
|
+
assert_credential(control.api()[1].auth, "INITKEY01")
|
|
554
|
+
|
|
555
|
+
refused_with({ "hashicorp" }, NOTAPROVIDER)
|
|
556
|
+
end)
|
|
557
|
+
|
|
558
|
+
it("a number in providers is refused, not dropped", function()
|
|
559
|
+
refused_with({ 42 }, NOTAPROVIDER)
|
|
560
|
+
end)
|
|
561
|
+
|
|
562
|
+
it("a nil hole in providers does not shorten the chain", function()
|
|
563
|
+
-- `{ spec, nil, spec }` ends ipairs after the first entry: the second
|
|
564
|
+
-- store would silently vanish. The feature walks to the highest key.
|
|
565
|
+
local providers = { memory("HOLE01") }
|
|
566
|
+
providers[3] = memory("HOLE03")
|
|
567
|
+
refused_with(providers, NOTAPROVIDER)
|
|
568
|
+
end)
|
|
569
|
+
|
|
570
|
+
it("a kind this SDK was not generated with is refused with sekreto's message", function()
|
|
571
|
+
-- `nosuchkind` is not built in and no plugin group provides it.
|
|
572
|
+
refused_with({ { kind = "nosuchkind" } }, "unknown provider kind: nosuchkind")
|
|
573
|
+
end)
|
|
574
|
+
|
|
575
|
+
it("an invalid secret name is refused, not skipped", function()
|
|
576
|
+
local w = make_wire()
|
|
577
|
+
local client = secrets_client(w, {
|
|
578
|
+
apikey = "OPTKEY01",
|
|
579
|
+
feature = chain({ memory("X") }, { name = "not a name" }),
|
|
580
|
+
})
|
|
581
|
+
local res = client:direct({ path = "/probe" })
|
|
582
|
+
assert.is_false(res.ok)
|
|
583
|
+
assert.is_truthy(tostring(res.err):find("sekreto: invalid name", 1, true),
|
|
584
|
+
"got: " .. tostring(res.err))
|
|
585
|
+
assert.are.equal(0, #w.api())
|
|
586
|
+
end)
|
|
587
|
+
end)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
-- What lua ships of the plugin tier depends on the groups the model
|
|
591
|
+
-- activated. Either way the boundary is asserted, and either way with
|
|
592
|
+
-- sekreto's own message.
|
|
593
|
+
describe("plugin kinds", function()
|
|
594
|
+
|
|
595
|
+
local has_hashicorp = pcall(require, "feature.secrets.sekreto.plugins.hashicorp")
|
|
596
|
+
|
|
597
|
+
if has_hashicorp then
|
|
598
|
+
it("the vault group's kinds are in this SDK's provider vocabulary", function()
|
|
599
|
+
local w = make_wire()
|
|
600
|
+
local client = secrets_client(w, { feature = chain({ memory("K") }) })
|
|
601
|
+
local sek = secrets_feature_of(client):sekreto()
|
|
602
|
+
assert.is_true(sek.catalog:has("hashicorp"))
|
|
603
|
+
assert.is_true(sek.catalog:has("boru"))
|
|
604
|
+
end)
|
|
605
|
+
|
|
606
|
+
it("the transport helper is built where net.lua looks for it", function()
|
|
607
|
+
local net = require("feature.secrets.sekreto.plugins.net")
|
|
608
|
+
local fh = io.open(net.HELPER, "rb")
|
|
609
|
+
assert.is_not_nil(fh, "make build did not produce " .. net.HELPER)
|
|
610
|
+
fh:close()
|
|
611
|
+
end)
|
|
612
|
+
|
|
613
|
+
it("a hashicorp provider reaches the helper and an unreachable vault is an ERROR", function()
|
|
614
|
+
-- Port 9 (discard) is closed on any sane host: the helper answers
|
|
615
|
+
-- "connection refused", httpjson raises sekreto's `cannot reach`,
|
|
616
|
+
-- and the request is refused with zero calls. A helper that was
|
|
617
|
+
-- never built answers "did not answer" instead - a different
|
|
618
|
+
-- message, so the assertion tells the two apart.
|
|
619
|
+
local w = make_wire()
|
|
620
|
+
local client = secrets_client(w, {
|
|
621
|
+
feature = chain({
|
|
622
|
+
{ kind = "hashicorp", addr = "http://127.0.0.1:9", token = "t0" },
|
|
623
|
+
}),
|
|
624
|
+
})
|
|
625
|
+
local res = client:direct({ path = "/probe" })
|
|
626
|
+
assert.is_false(res.ok)
|
|
627
|
+
local msg = tostring(res.err)
|
|
628
|
+
assert.is_truthy(msg:find("sekreto: cannot reach ", 1, true)
|
|
629
|
+
and msg:find("127.0.0.1:9", 1, true)
|
|
630
|
+
and msg:find("connection refused", 1, true),
|
|
631
|
+
"expected the transport failure from the helper, got: " .. msg)
|
|
632
|
+
assert.is_falsy(msg:find("did not answer", 1, true),
|
|
633
|
+
"the helper was not run: " .. msg)
|
|
634
|
+
assert.are.equal(0, #w.api())
|
|
635
|
+
end)
|
|
636
|
+
else
|
|
637
|
+
it("a plugin kind that was not generated is refused with sekreto's message", function()
|
|
638
|
+
local w = make_wire()
|
|
639
|
+
local client = secrets_client(w, {
|
|
640
|
+
feature = chain({
|
|
641
|
+
{ kind = "hashicorp", addr = "http://127.0.0.1:9", token = "t0" },
|
|
642
|
+
}),
|
|
643
|
+
})
|
|
644
|
+
local res = client:direct({ path = "/probe" })
|
|
645
|
+
assert.is_false(res.ok)
|
|
646
|
+
assert.is_truthy(tostring(res.err):find(
|
|
647
|
+
"hashicorp is a sekreto plugin, not built in", 1, true),
|
|
648
|
+
"got: " .. tostring(res.err))
|
|
649
|
+
assert.are.equal(0, #w.api())
|
|
650
|
+
end)
|
|
651
|
+
end
|
|
652
|
+
end)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
-- The one auth suppression lua can express.
|
|
656
|
+
describe("auth suppression", function()
|
|
657
|
+
|
|
658
|
+
it("clearing options.auth suppresses the chain credential on the entity path", function()
|
|
659
|
+
local w = make_wire()
|
|
660
|
+
local client = secrets_client(w, { feature = chain({ memory("LEAK01") }) })
|
|
661
|
+
client.options.auth = nil
|
|
662
|
+
|
|
663
|
+
drive_entity_op(client, w)
|
|
664
|
+
assert.is_false(w.api()[1].has,
|
|
665
|
+
"suppressed auth must send no credential, got " .. tostring(w.api()[1].auth))
|
|
666
|
+
end)
|
|
667
|
+
|
|
668
|
+
it("clearing options.auth suppresses the chain credential on direct()", function()
|
|
669
|
+
local w = make_wire()
|
|
670
|
+
local client = secrets_client(w, { feature = chain({ memory("LEAK02") }) })
|
|
671
|
+
client.options.auth = nil
|
|
672
|
+
|
|
673
|
+
local res = client:direct({ path = "/probe" })
|
|
674
|
+
assert.is_true(res.ok)
|
|
675
|
+
assert.is_false(w.api()[1].has, "got " .. tostring(w.api()[1].auth))
|
|
676
|
+
end)
|
|
677
|
+
|
|
678
|
+
it("an options-level auth = nil is NOT expressible: validate fills the default", function()
|
|
679
|
+
-- Pinned so the limitation is a recorded fact rather than a surprise:
|
|
680
|
+
-- `{ auth = nil }` is the same table as `{}`, and make_options then
|
|
681
|
+
-- supplies the optspec default. The post-construction clear above is
|
|
682
|
+
-- the suppression this target has.
|
|
683
|
+
local client = sdk.test(nil, { apikey = "OPTKEY01", auth = nil })
|
|
684
|
+
assert.is_table(client:options_map().auth)
|
|
685
|
+
end)
|
|
686
|
+
end)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
describe("cache", function()
|
|
690
|
+
|
|
691
|
+
it("caches the resolved credential by default", function()
|
|
692
|
+
local asked = 0
|
|
693
|
+
local w = make_wire()
|
|
694
|
+
local client = secrets_client(w, {
|
|
695
|
+
feature = chain({
|
|
696
|
+
custom_provider(function(_name) asked = asked + 1; return "V" .. asked end),
|
|
697
|
+
}),
|
|
698
|
+
})
|
|
699
|
+
client:direct({ path = "/a" })
|
|
700
|
+
client:direct({ path = "/b" })
|
|
701
|
+
assert.are.equal(2, #w.api())
|
|
702
|
+
assert.are.equal(1, asked, "the chain must be asked once while caching")
|
|
703
|
+
assert_credential(w.api()[2].auth, "V1")
|
|
704
|
+
end)
|
|
705
|
+
|
|
706
|
+
it("cache = false asks the chain once per request", function()
|
|
707
|
+
local asked = 0
|
|
708
|
+
local w = make_wire()
|
|
709
|
+
local client = secrets_client(w, {
|
|
710
|
+
feature = chain({
|
|
711
|
+
custom_provider(function(_name) asked = asked + 1; return "V" .. asked end),
|
|
712
|
+
}, { cache = false }),
|
|
713
|
+
})
|
|
714
|
+
client:direct({ path = "/a" })
|
|
715
|
+
client:direct({ path = "/b" })
|
|
716
|
+
assert.are.equal(2, asked)
|
|
717
|
+
assert_credential(w.api()[1].auth, "V1")
|
|
718
|
+
assert_credential(w.api()[2].auth, "V2")
|
|
719
|
+
end)
|
|
720
|
+
|
|
721
|
+
it("an uncached miss after a hit RETRACTS the credential", function()
|
|
722
|
+
local value = "REVOCABLE01"
|
|
723
|
+
local w = make_wire()
|
|
724
|
+
local client = secrets_client(w, {
|
|
725
|
+
feature = chain({
|
|
726
|
+
custom_provider(function(_name) return value end),
|
|
727
|
+
}, { cache = false }),
|
|
728
|
+
})
|
|
729
|
+
client:direct({ path = "/a" })
|
|
730
|
+
assert_credential(w.api()[1].auth, "REVOCABLE01")
|
|
731
|
+
|
|
732
|
+
value = nil
|
|
733
|
+
client:direct({ path = "/b" })
|
|
734
|
+
assert.is_false(w.api()[2].has,
|
|
735
|
+
"after the chain reports a miss the retracted credential must not go out; the wire saw " ..
|
|
736
|
+
tostring(w.api()[2].auth))
|
|
737
|
+
end)
|
|
738
|
+
end)
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
describe("sekreto accessor", function()
|
|
742
|
+
|
|
743
|
+
it("is the live instance", function()
|
|
744
|
+
local w = make_wire()
|
|
745
|
+
local client = secrets_client(w, { feature = chain({ memory("LIVEKEY0001") }) })
|
|
746
|
+
local sf = secrets_feature_of(client)
|
|
747
|
+
assert.are.equal(sf:sekreto(), sf:sekreto())
|
|
748
|
+
assert.are.equal("LIVEKEY0001", sf:sekreto():get("apikey"))
|
|
749
|
+
local redacted = sf:sekreto():redact("token LIVEKEY0001 here")
|
|
750
|
+
assert.is_truthy(redacted:find("[redacted]", 1, true),
|
|
751
|
+
"redact did not cover a resolved value: " .. redacted)
|
|
752
|
+
end)
|
|
753
|
+
end)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
describe("exchange", function()
|
|
757
|
+
|
|
758
|
+
local function exchange_client(w, extra, sdkextra)
|
|
759
|
+
local fopts = {
|
|
760
|
+
providers = { { kind = "memory", values = { REFRESH_TOKEN = "REFRESH01" } } },
|
|
761
|
+
name = "refresh_token",
|
|
762
|
+
exchange = { active = true, path = "auth/token" },
|
|
763
|
+
}
|
|
764
|
+
for k, v in pairs(extra or {}) do fopts[k] = v end
|
|
765
|
+
local sdkopts = { feature = { secrets = fopts } }
|
|
766
|
+
for k, v in pairs(sdkextra or {}) do sdkopts[k] = v end
|
|
767
|
+
-- `active` is set here so an `exchange` override above can replace
|
|
768
|
+
-- the whole block without dropping it.
|
|
769
|
+
sdkopts.feature.secrets.active = true
|
|
770
|
+
return secrets_client(w, sdkopts)
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
it("buys an access token with the resolved refresh token", function()
|
|
774
|
+
local w = make_wire()
|
|
775
|
+
local client = exchange_client(w)
|
|
776
|
+
local res = client:direct({ path = "/probe" })
|
|
777
|
+
assert.is_true(res.ok, tostring(res.err))
|
|
778
|
+
|
|
779
|
+
assert.are.equal(1, #w.token(), "expected one token purchase")
|
|
780
|
+
local t = w.token()[1]
|
|
781
|
+
assert.are.equal("POST", t.method)
|
|
782
|
+
assert.is_truthy(t.body:find('"refresh_token":"REFRESH01"', 1, true),
|
|
783
|
+
"the refresh token must be sent as JSON: " .. tostring(t.body))
|
|
784
|
+
assert_credential(w.api()[1].auth, "ACCESS01")
|
|
785
|
+
end)
|
|
786
|
+
|
|
787
|
+
it("a spent token is repurchased and the request retried once", function()
|
|
788
|
+
local w = make_wire({ apistatus = { 401, 200 } })
|
|
789
|
+
local client = exchange_client(w)
|
|
790
|
+
local res = client:direct({ path = "/probe" })
|
|
791
|
+
assert.is_true(res.ok, tostring(res.err))
|
|
792
|
+
|
|
793
|
+
assert.are.equal(2, #w.api(), "expected the refused request and one retry")
|
|
794
|
+
assert_credential(w.api()[1].auth, "ACCESS01")
|
|
795
|
+
assert_credential(w.api()[2].auth, "ACCESS02")
|
|
796
|
+
assert.are.equal(2, #w.token())
|
|
797
|
+
end)
|
|
798
|
+
|
|
799
|
+
it("a second refusal surfaces rather than spinning", function()
|
|
800
|
+
local w = make_wire({ apistatus = { 401 } })
|
|
801
|
+
local client = exchange_client(w)
|
|
802
|
+
local res = client:direct({ path = "/probe" })
|
|
803
|
+
assert.is_false(res.ok)
|
|
804
|
+
assert.are.equal(401, res.status)
|
|
805
|
+
assert.are.equal(2, #w.api(), "retries = 1 means exactly one retry")
|
|
806
|
+
end)
|
|
807
|
+
|
|
808
|
+
it("retries = 0 never repurchases", function()
|
|
809
|
+
local w = make_wire({ apistatus = { 401 } })
|
|
810
|
+
local client = exchange_client(w, { exchange = { active = true, retries = 0 } })
|
|
811
|
+
local res = client:direct({ path = "/probe" })
|
|
812
|
+
assert.is_false(res.ok)
|
|
813
|
+
assert.are.equal(1, #w.api())
|
|
814
|
+
assert.are.equal(1, #w.token(), "the initial purchase only")
|
|
815
|
+
end)
|
|
816
|
+
|
|
817
|
+
it("exchange.refresh seats first, as the explicit credential", function()
|
|
818
|
+
local w = make_wire()
|
|
819
|
+
local client = exchange_client(w, { exchange = { active = true, refresh = "EXPLICIT01" } })
|
|
820
|
+
client:direct({ path = "/probe" })
|
|
821
|
+
assert.is_truthy(w.token()[1].body:find('"refresh_token":"EXPLICIT01"', 1, true),
|
|
822
|
+
tostring(w.token()[1].body))
|
|
823
|
+
end)
|
|
824
|
+
|
|
825
|
+
it("a held apikey is spent before anything is bought", function()
|
|
826
|
+
local w = make_wire()
|
|
827
|
+
local client = exchange_client(w, nil, { apikey = "HELD01" })
|
|
828
|
+
client:direct({ path = "/probe" })
|
|
829
|
+
assert.are.equal(0, #w.token(), "a held access token must be spent first")
|
|
830
|
+
assert_credential(w.api()[1].auth, "HELD01")
|
|
831
|
+
end)
|
|
832
|
+
|
|
833
|
+
it("no refresh token anywhere is a refusal with the feature's message", function()
|
|
834
|
+
local w = make_wire()
|
|
835
|
+
local client = exchange_client(w, {
|
|
836
|
+
providers = { { kind = "memory", values = {} } },
|
|
837
|
+
})
|
|
838
|
+
local res = client:direct({ path = "/probe" })
|
|
839
|
+
assert.is_false(res.ok)
|
|
840
|
+
assert.is_truthy(tostring(res.err):find("secrets: no refresh token", 1, true),
|
|
841
|
+
tostring(res.err))
|
|
842
|
+
assert.are.equal(0, #w.api())
|
|
843
|
+
end)
|
|
844
|
+
|
|
845
|
+
it("test mode buys nothing", function()
|
|
846
|
+
-- A LIVE construction (so the feature is installed through extend),
|
|
847
|
+
-- switched to test mode before the first request: the wrapper runs
|
|
848
|
+
-- before the mode block in the base fetcher, so the purchase is what
|
|
849
|
+
-- test mode sees - and it must answer the deterministic fake token
|
|
850
|
+
-- without touching the token endpoint.
|
|
851
|
+
local w = make_wire()
|
|
852
|
+
local client = exchange_client(w)
|
|
853
|
+
local sf = secrets_feature_of(client)
|
|
854
|
+
client.mode = "test"
|
|
855
|
+
|
|
856
|
+
local res = client:direct({ path = "/probe" })
|
|
857
|
+
assert.is_false(res.ok, "test mode must block the request itself")
|
|
858
|
+
assert.are.equal("test-access_token", sf:credential())
|
|
859
|
+
assert.are.equal(0, #w.token(), "test mode must not reach the token endpoint")
|
|
860
|
+
end)
|
|
861
|
+
end)
|
|
862
|
+
end)
|