From 519c00f47703f966bfcb09c57392bc5895c6df5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:43:08 +0000 Subject: [PATCH 1/9] Initial plan From 2d31c18d86806952073e492484cac6faef38cc20 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:59:53 +0000 Subject: [PATCH 2/9] Vendor wasmcloud:secrets WIT files to avoid registry authentication Co-authored-by: lachieh <1687902+lachieh@users.noreply.github.com> --- .../http-password-checker/.gitignore | 3 +- .../http-password-checker/wasmcloud.lock | 9 ----- .../wit/deps/secrets/reveal.wit | 9 +++++ .../wit/deps/secrets/store.wit | 38 +++++++++++++++++++ 4 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 examples/components/http-password-checker/wit/deps/secrets/reveal.wit create mode 100644 examples/components/http-password-checker/wit/deps/secrets/store.wit diff --git a/examples/components/http-password-checker/.gitignore b/examples/components/http-password-checker/.gitignore index 0c6f82ea..9e8e2820 100644 --- a/examples/components/http-password-checker/.gitignore +++ b/examples/components/http-password-checker/.gitignore @@ -7,4 +7,5 @@ # wash build output /build/* -wit/deps/ +# wit/deps/ is vendored to avoid registry authentication issues +# wit/deps/ diff --git a/examples/components/http-password-checker/wasmcloud.lock b/examples/components/http-password-checker/wasmcloud.lock index 810bbb75..32104997 100644 --- a/examples/components/http-password-checker/wasmcloud.lock +++ b/examples/components/http-password-checker/wasmcloud.lock @@ -10,12 +10,3 @@ registry = "wasi.dev" requirement = "=0.2.2" version = "0.2.2" digest = "sha256:a1f129cdf1fde55ec2d4ae8d998c39a7e5cf7544a8bd84a831054ac0d2ac64dd" - -[[packages]] -name = "wasmcloud:secrets" -registry = "wasmcloud.com" - -[[packages.versions]] -requirement = "=0.1.0-draft" -version = "0.1.0-draft" -digest = "sha256:aab5d8d6ecbc17d8237f269e05170fabcf8704738c60b8f1bf09a13fc6855ffb" diff --git a/examples/components/http-password-checker/wit/deps/secrets/reveal.wit b/examples/components/http-password-checker/wit/deps/secrets/reveal.wit new file mode 100644 index 00000000..27c6e2b9 --- /dev/null +++ b/examples/components/http-password-checker/wit/deps/secrets/reveal.wit @@ -0,0 +1,9 @@ +package wasmcloud:secrets@0.1.0-draft; + +interface reveal { + use store.{secret, secret-value}; + + /// Reveals the value of a secret to the caller. + /// This lets you easily audit your code to discover where secrets are being used. + reveal: func(s: secret) -> secret-value; +} diff --git a/examples/components/http-password-checker/wit/deps/secrets/store.wit b/examples/components/http-password-checker/wit/deps/secrets/store.wit new file mode 100644 index 00000000..056674a3 --- /dev/null +++ b/examples/components/http-password-checker/wit/deps/secrets/store.wit @@ -0,0 +1,38 @@ +package wasmcloud:secrets@0.1.0-draft; + +/// An error type that encapsulates the different errors that can occur fetching secrets +interface store { + /// An opaque type to represent a secret stored by a secrets backend + resource secret; + + /// A secret value can be either a string or a byte array, which lets you + /// store binary data as a secret. + variant secret-value { + /// A string value + string(string), + /// A byte array value + bytes(list), + } + + /// An error type that encapsulates the different errors that can occur fetching secrets + variant secrets-error { + /// This indicates an error from an "upstream" secrets source. + /// As this could be almost _anything_ (such as Vault, Kubernetes Secrets, KeyValue buckets, etc), + /// the error message is a string. + upstream(string), + /// This indicates an error from an I/O operation. + /// As this could be almost _anything_ (such as a file read, network connection, etc), + /// the error message is a string. + /// Depending on how this ends up being consumed, + /// we may consider moving this to use the `wasi:io/error` type instead. + /// For simplicity right now in supporting multiple implementations, it is being left as a string. + io(string), + /// This indicates that the secret was not found. Generally "not found" errors will + /// be handled by the upstream secrets backend, but there are cases where the host + /// may need to return this error. + not-found, + } + + /// Gets a single opaque secrets value set at the given key if it exists + get: func(key: string) -> result; +} From d2a161d9f7e896229d0f0edefad4581b258407a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:05:09 +0000 Subject: [PATCH 3/9] Fix comment placement in store.wit interface Co-authored-by: lachieh <1687902+lachieh@users.noreply.github.com> --- .../components/http-password-checker/wit/deps/secrets/store.wit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/components/http-password-checker/wit/deps/secrets/store.wit b/examples/components/http-password-checker/wit/deps/secrets/store.wit index 056674a3..202c7aba 100644 --- a/examples/components/http-password-checker/wit/deps/secrets/store.wit +++ b/examples/components/http-password-checker/wit/deps/secrets/store.wit @@ -1,6 +1,6 @@ package wasmcloud:secrets@0.1.0-draft; -/// An error type that encapsulates the different errors that can occur fetching secrets +/// Interface for interacting with a secrets store backend interface store { /// An opaque type to represent a secret stored by a secrets backend resource secret; From afa1113c465c74d4a2c44bc8e73bdeab74a33c78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:06:06 +0000 Subject: [PATCH 4/9] Improve WIT documentation comments for consistency Co-authored-by: lachieh <1687902+lachieh@users.noreply.github.com> --- .../http-password-checker/wit/deps/secrets/store.wit | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/components/http-password-checker/wit/deps/secrets/store.wit b/examples/components/http-password-checker/wit/deps/secrets/store.wit index 202c7aba..d2ede245 100644 --- a/examples/components/http-password-checker/wit/deps/secrets/store.wit +++ b/examples/components/http-password-checker/wit/deps/secrets/store.wit @@ -17,7 +17,7 @@ interface store { /// An error type that encapsulates the different errors that can occur fetching secrets variant secrets-error { /// This indicates an error from an "upstream" secrets source. - /// As this could be almost _anything_ (such as Vault, Kubernetes Secrets, KeyValue buckets, etc), + /// As this could be almost _anything_ (such as Vault, Kubernetes Secrets, Key-Value buckets, etc), /// the error message is a string. upstream(string), /// This indicates an error from an I/O operation. @@ -33,6 +33,6 @@ interface store { not-found, } - /// Gets a single opaque secrets value set at the given key if it exists + /// Gets a single opaque secret value set at the given key if it exists get: func(key: string) -> result; } From 403d554f022e6d9ac6eb689fb68f8ab51fc9999b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:01:03 +0000 Subject: [PATCH 5/9] Use GITHUB_TOKEN for wash build registry authentication instead of vendoring Co-authored-by: lachieh <1687902+lachieh@users.noreply.github.com> --- .github/workflows/examples_component.yml | 3 ++ .../http-password-checker/.gitignore | 3 +- .../http-password-checker/wasmcloud.lock | 9 +++++ .../wit/deps/secrets/reveal.wit | 9 ----- .../wit/deps/secrets/store.wit | 38 ------------------- 5 files changed, 13 insertions(+), 49 deletions(-) delete mode 100644 examples/components/http-password-checker/wit/deps/secrets/reveal.wit delete mode 100644 examples/components/http-password-checker/wit/deps/secrets/store.wit diff --git a/.github/workflows/examples_component.yml b/.github/workflows/examples_component.yml index 0ff15cbf..314cf9d3 100644 --- a/.github/workflows/examples_component.yml +++ b/.github/workflows/examples_component.yml @@ -92,6 +92,9 @@ jobs: - name: wash build working-directory: './examples/components/${{ inputs.folder }}' + env: + WASH_REG_USER: ${{ github.repository_owner }} + WASH_REG_PASSWORD: ${{ secrets.GITHUB_TOKEN }} run: wash build - name: run tests diff --git a/examples/components/http-password-checker/.gitignore b/examples/components/http-password-checker/.gitignore index 9e8e2820..0c6f82ea 100644 --- a/examples/components/http-password-checker/.gitignore +++ b/examples/components/http-password-checker/.gitignore @@ -7,5 +7,4 @@ # wash build output /build/* -# wit/deps/ is vendored to avoid registry authentication issues -# wit/deps/ +wit/deps/ diff --git a/examples/components/http-password-checker/wasmcloud.lock b/examples/components/http-password-checker/wasmcloud.lock index 32104997..810bbb75 100644 --- a/examples/components/http-password-checker/wasmcloud.lock +++ b/examples/components/http-password-checker/wasmcloud.lock @@ -10,3 +10,12 @@ registry = "wasi.dev" requirement = "=0.2.2" version = "0.2.2" digest = "sha256:a1f129cdf1fde55ec2d4ae8d998c39a7e5cf7544a8bd84a831054ac0d2ac64dd" + +[[packages]] +name = "wasmcloud:secrets" +registry = "wasmcloud.com" + +[[packages.versions]] +requirement = "=0.1.0-draft" +version = "0.1.0-draft" +digest = "sha256:aab5d8d6ecbc17d8237f269e05170fabcf8704738c60b8f1bf09a13fc6855ffb" diff --git a/examples/components/http-password-checker/wit/deps/secrets/reveal.wit b/examples/components/http-password-checker/wit/deps/secrets/reveal.wit deleted file mode 100644 index 27c6e2b9..00000000 --- a/examples/components/http-password-checker/wit/deps/secrets/reveal.wit +++ /dev/null @@ -1,9 +0,0 @@ -package wasmcloud:secrets@0.1.0-draft; - -interface reveal { - use store.{secret, secret-value}; - - /// Reveals the value of a secret to the caller. - /// This lets you easily audit your code to discover where secrets are being used. - reveal: func(s: secret) -> secret-value; -} diff --git a/examples/components/http-password-checker/wit/deps/secrets/store.wit b/examples/components/http-password-checker/wit/deps/secrets/store.wit deleted file mode 100644 index d2ede245..00000000 --- a/examples/components/http-password-checker/wit/deps/secrets/store.wit +++ /dev/null @@ -1,38 +0,0 @@ -package wasmcloud:secrets@0.1.0-draft; - -/// Interface for interacting with a secrets store backend -interface store { - /// An opaque type to represent a secret stored by a secrets backend - resource secret; - - /// A secret value can be either a string or a byte array, which lets you - /// store binary data as a secret. - variant secret-value { - /// A string value - string(string), - /// A byte array value - bytes(list), - } - - /// An error type that encapsulates the different errors that can occur fetching secrets - variant secrets-error { - /// This indicates an error from an "upstream" secrets source. - /// As this could be almost _anything_ (such as Vault, Kubernetes Secrets, Key-Value buckets, etc), - /// the error message is a string. - upstream(string), - /// This indicates an error from an I/O operation. - /// As this could be almost _anything_ (such as a file read, network connection, etc), - /// the error message is a string. - /// Depending on how this ends up being consumed, - /// we may consider moving this to use the `wasi:io/error` type instead. - /// For simplicity right now in supporting multiple implementations, it is being left as a string. - io(string), - /// This indicates that the secret was not found. Generally "not found" errors will - /// be handled by the upstream secrets backend, but there are cases where the host - /// may need to return this error. - not-found, - } - - /// Gets a single opaque secret value set at the given key if it exists - get: func(key: string) -> result; -} From db6b418d27e7afdd234d8e2975fd0e5adbd55a22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:48:41 +0000 Subject: [PATCH 6/9] Add GITHUB_TOKEN environment variable to wash build step Co-authored-by: lachieh <1687902+lachieh@users.noreply.github.com> --- .github/workflows/examples_component.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/examples_component.yml b/.github/workflows/examples_component.yml index 314cf9d3..4d287661 100644 --- a/.github/workflows/examples_component.yml +++ b/.github/workflows/examples_component.yml @@ -93,6 +93,7 @@ jobs: - name: wash build working-directory: './examples/components/${{ inputs.folder }}' env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WASH_REG_USER: ${{ github.repository_owner }} WASH_REG_PASSWORD: ${{ secrets.GITHUB_TOKEN }} run: wash build From a0378b6bce59f7eff101424ebda5e73a09bd60f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 01:19:11 +0000 Subject: [PATCH 7/9] Revert to vendoring approach - GITHUB_TOKEN doesn't work for warg registry Co-authored-by: lachieh <1687902+lachieh@users.noreply.github.com> --- .github/workflows/examples_component.yml | 4 -- .../http-password-checker/.gitignore | 3 +- .../http-password-checker/wasmcloud.lock | 9 ----- .../wit/deps/secrets/reveal.wit | 9 +++++ .../wit/deps/secrets/store.wit | 38 +++++++++++++++++++ 5 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 examples/components/http-password-checker/wit/deps/secrets/reveal.wit create mode 100644 examples/components/http-password-checker/wit/deps/secrets/store.wit diff --git a/.github/workflows/examples_component.yml b/.github/workflows/examples_component.yml index 4d287661..0ff15cbf 100644 --- a/.github/workflows/examples_component.yml +++ b/.github/workflows/examples_component.yml @@ -92,10 +92,6 @@ jobs: - name: wash build working-directory: './examples/components/${{ inputs.folder }}' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WASH_REG_USER: ${{ github.repository_owner }} - WASH_REG_PASSWORD: ${{ secrets.GITHUB_TOKEN }} run: wash build - name: run tests diff --git a/examples/components/http-password-checker/.gitignore b/examples/components/http-password-checker/.gitignore index 0c6f82ea..9e8e2820 100644 --- a/examples/components/http-password-checker/.gitignore +++ b/examples/components/http-password-checker/.gitignore @@ -7,4 +7,5 @@ # wash build output /build/* -wit/deps/ +# wit/deps/ is vendored to avoid registry authentication issues +# wit/deps/ diff --git a/examples/components/http-password-checker/wasmcloud.lock b/examples/components/http-password-checker/wasmcloud.lock index 810bbb75..32104997 100644 --- a/examples/components/http-password-checker/wasmcloud.lock +++ b/examples/components/http-password-checker/wasmcloud.lock @@ -10,12 +10,3 @@ registry = "wasi.dev" requirement = "=0.2.2" version = "0.2.2" digest = "sha256:a1f129cdf1fde55ec2d4ae8d998c39a7e5cf7544a8bd84a831054ac0d2ac64dd" - -[[packages]] -name = "wasmcloud:secrets" -registry = "wasmcloud.com" - -[[packages.versions]] -requirement = "=0.1.0-draft" -version = "0.1.0-draft" -digest = "sha256:aab5d8d6ecbc17d8237f269e05170fabcf8704738c60b8f1bf09a13fc6855ffb" diff --git a/examples/components/http-password-checker/wit/deps/secrets/reveal.wit b/examples/components/http-password-checker/wit/deps/secrets/reveal.wit new file mode 100644 index 00000000..27c6e2b9 --- /dev/null +++ b/examples/components/http-password-checker/wit/deps/secrets/reveal.wit @@ -0,0 +1,9 @@ +package wasmcloud:secrets@0.1.0-draft; + +interface reveal { + use store.{secret, secret-value}; + + /// Reveals the value of a secret to the caller. + /// This lets you easily audit your code to discover where secrets are being used. + reveal: func(s: secret) -> secret-value; +} diff --git a/examples/components/http-password-checker/wit/deps/secrets/store.wit b/examples/components/http-password-checker/wit/deps/secrets/store.wit new file mode 100644 index 00000000..d2ede245 --- /dev/null +++ b/examples/components/http-password-checker/wit/deps/secrets/store.wit @@ -0,0 +1,38 @@ +package wasmcloud:secrets@0.1.0-draft; + +/// Interface for interacting with a secrets store backend +interface store { + /// An opaque type to represent a secret stored by a secrets backend + resource secret; + + /// A secret value can be either a string or a byte array, which lets you + /// store binary data as a secret. + variant secret-value { + /// A string value + string(string), + /// A byte array value + bytes(list), + } + + /// An error type that encapsulates the different errors that can occur fetching secrets + variant secrets-error { + /// This indicates an error from an "upstream" secrets source. + /// As this could be almost _anything_ (such as Vault, Kubernetes Secrets, Key-Value buckets, etc), + /// the error message is a string. + upstream(string), + /// This indicates an error from an I/O operation. + /// As this could be almost _anything_ (such as a file read, network connection, etc), + /// the error message is a string. + /// Depending on how this ends up being consumed, + /// we may consider moving this to use the `wasi:io/error` type instead. + /// For simplicity right now in supporting multiple implementations, it is being left as a string. + io(string), + /// This indicates that the secret was not found. Generally "not found" errors will + /// be handled by the upstream secrets backend, but there are cases where the host + /// may need to return this error. + not-found, + } + + /// Gets a single opaque secret value set at the given key if it exists + get: func(key: string) -> result; +} From 7aad0fc6eeb1ac003b042cf9f6382d8ef372564e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:15:05 +0000 Subject: [PATCH 8/9] Fix auth by adding packages: read permission and GITHUB_TOKEN to wash build Co-authored-by: lachieh <1687902+lachieh@users.noreply.github.com> --- .github/workflows/examples_.yml | 1 + .github/workflows/examples_component.yml | 2 + .../http-password-checker/.gitignore | 3 +- .../http-password-checker/wasmcloud.lock | 9 +++++ .../wit/deps/secrets/reveal.wit | 9 ----- .../wit/deps/secrets/store.wit | 38 ------------------- 6 files changed, 13 insertions(+), 49 deletions(-) delete mode 100644 examples/components/http-password-checker/wit/deps/secrets/reveal.wit delete mode 100644 examples/components/http-password-checker/wit/deps/secrets/store.wit diff --git a/.github/workflows/examples_.yml b/.github/workflows/examples_.yml index 75d4ae5c..ab6e1fe6 100644 --- a/.github/workflows/examples_.yml +++ b/.github/workflows/examples_.yml @@ -12,6 +12,7 @@ on: permissions: contents: read + packages: read jobs: meta: diff --git a/.github/workflows/examples_component.yml b/.github/workflows/examples_component.yml index 0ff15cbf..dec2c9dd 100644 --- a/.github/workflows/examples_component.yml +++ b/.github/workflows/examples_component.yml @@ -92,6 +92,8 @@ jobs: - name: wash build working-directory: './examples/components/${{ inputs.folder }}' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: wash build - name: run tests diff --git a/examples/components/http-password-checker/.gitignore b/examples/components/http-password-checker/.gitignore index 9e8e2820..0c6f82ea 100644 --- a/examples/components/http-password-checker/.gitignore +++ b/examples/components/http-password-checker/.gitignore @@ -7,5 +7,4 @@ # wash build output /build/* -# wit/deps/ is vendored to avoid registry authentication issues -# wit/deps/ +wit/deps/ diff --git a/examples/components/http-password-checker/wasmcloud.lock b/examples/components/http-password-checker/wasmcloud.lock index 32104997..810bbb75 100644 --- a/examples/components/http-password-checker/wasmcloud.lock +++ b/examples/components/http-password-checker/wasmcloud.lock @@ -10,3 +10,12 @@ registry = "wasi.dev" requirement = "=0.2.2" version = "0.2.2" digest = "sha256:a1f129cdf1fde55ec2d4ae8d998c39a7e5cf7544a8bd84a831054ac0d2ac64dd" + +[[packages]] +name = "wasmcloud:secrets" +registry = "wasmcloud.com" + +[[packages.versions]] +requirement = "=0.1.0-draft" +version = "0.1.0-draft" +digest = "sha256:aab5d8d6ecbc17d8237f269e05170fabcf8704738c60b8f1bf09a13fc6855ffb" diff --git a/examples/components/http-password-checker/wit/deps/secrets/reveal.wit b/examples/components/http-password-checker/wit/deps/secrets/reveal.wit deleted file mode 100644 index 27c6e2b9..00000000 --- a/examples/components/http-password-checker/wit/deps/secrets/reveal.wit +++ /dev/null @@ -1,9 +0,0 @@ -package wasmcloud:secrets@0.1.0-draft; - -interface reveal { - use store.{secret, secret-value}; - - /// Reveals the value of a secret to the caller. - /// This lets you easily audit your code to discover where secrets are being used. - reveal: func(s: secret) -> secret-value; -} diff --git a/examples/components/http-password-checker/wit/deps/secrets/store.wit b/examples/components/http-password-checker/wit/deps/secrets/store.wit deleted file mode 100644 index d2ede245..00000000 --- a/examples/components/http-password-checker/wit/deps/secrets/store.wit +++ /dev/null @@ -1,38 +0,0 @@ -package wasmcloud:secrets@0.1.0-draft; - -/// Interface for interacting with a secrets store backend -interface store { - /// An opaque type to represent a secret stored by a secrets backend - resource secret; - - /// A secret value can be either a string or a byte array, which lets you - /// store binary data as a secret. - variant secret-value { - /// A string value - string(string), - /// A byte array value - bytes(list), - } - - /// An error type that encapsulates the different errors that can occur fetching secrets - variant secrets-error { - /// This indicates an error from an "upstream" secrets source. - /// As this could be almost _anything_ (such as Vault, Kubernetes Secrets, Key-Value buckets, etc), - /// the error message is a string. - upstream(string), - /// This indicates an error from an I/O operation. - /// As this could be almost _anything_ (such as a file read, network connection, etc), - /// the error message is a string. - /// Depending on how this ends up being consumed, - /// we may consider moving this to use the `wasi:io/error` type instead. - /// For simplicity right now in supporting multiple implementations, it is being left as a string. - io(string), - /// This indicates that the secret was not found. Generally "not found" errors will - /// be handled by the upstream secrets backend, but there are cases where the host - /// may need to return this error. - not-found, - } - - /// Gets a single opaque secret value set at the given key if it exists - get: func(key: string) -> result; -} From 69b8ceb0e1db3ca6aeaec643f68dca20d0fe6aa3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:29:29 +0000 Subject: [PATCH 9/9] Use WASH_REG_USER and WASH_REG_PASSWORD for OCI registry auth Co-authored-by: lachieh <1687902+lachieh@users.noreply.github.com> --- .github/workflows/examples_component.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/examples_component.yml b/.github/workflows/examples_component.yml index dec2c9dd..314cf9d3 100644 --- a/.github/workflows/examples_component.yml +++ b/.github/workflows/examples_component.yml @@ -93,7 +93,8 @@ jobs: - name: wash build working-directory: './examples/components/${{ inputs.folder }}' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WASH_REG_USER: ${{ github.repository_owner }} + WASH_REG_PASSWORD: ${{ secrets.GITHUB_TOKEN }} run: wash build - name: run tests