## Summary Fixes #72 - The `jfrog-oauth` module now fails with a clear error message when the JFrog access token is empty, instead of silently creating configurations with empty tokens. ## Changes ### 1. Added Precondition Validation (`main.tf`) ```hcl lifecycle { precondition { condition = data.coder_external_auth.jfrog.access_token != "" error_message = "JFrog access token is empty. Please authenticate with JFrog using external auth." } } ``` This ensures the module fails at **plan time** with a clear error when users haven't authenticated via external auth. ### 2. Replaced `main.test.ts` with `jfrog-oauth.tftest.hcl` **Why we removed the TypeScript tests:** The TypeScript tests used `runTerraformApply()` which runs `terraform apply` directly. This approach **cannot mock data sources** like `coder_external_auth`. The Coder provider returns empty strings for tokens by default when running outside a real Coder workspace. With our new precondition, the TypeScript tests would always fail because: 1. `terraform apply` runs → empty `access_token` from mock provider 2. Precondition check fails → "JFrog access token is empty" 3. Test fails before any assertions run **The solution:** Terraform's native `.tftest.hcl` format supports `override_data` blocks that can properly mock data sources: ```hcl override_data { target = data.coder_external_auth.jfrog values = { access_token = "valid-token-value" # or "" to test failure } } ``` ### 3. Comprehensive Test Coverage The new `jfrog-oauth.tftest.hcl` includes **12 tests** (up from 7): | Test | What it validates | |------|------------------| | `test_required_vars` | Basic module works with required variables | | `test_empty_access_token_fails` | **NEW:** Precondition rejects empty tokens | | `test_valid_access_token_succeeds` | Module works with valid token | | `test_jfrog_url_validation` | **NEW:** URL must start with http(s):// | | `test_username_field_validation` | **NEW:** Must be "email" or "username" | | `test_with_npm_package_manager` | NPM config with scoped repos (script content) | | `test_configure_code_server` | **NEW:** IDE env vars created when enabled | | `test_go_proxy_env` | GOPROXY env value with multiple repos | | `test_pypi_package_manager` | pip.conf with extra-index-url | | `test_docker_package_manager` | register_docker commands for all repos | | `test_conda_package_manager` | .condarc channels configuration | | `test_maven_package_manager` | settings.xml with servers and repos | All package manager tests use `strcontains()` to verify the actual script content matches expected configuration formats. ## Test Limitations (Acknowledged) The tests verify **template rendering** but not **runtime execution**: | ✅ What we test | ❌ What we don't test | |----------------|----------------------| | Configuration file formats | Script syntax errors at runtime | | Variable interpolation | JFrog CLI compatibility | | Precondition validation | Actual JFrog authentication | | Script contains expected content | Commands execute successfully | **Rationale:** The original TypeScript tests also only checked script content (`toContain()`), not execution. Full execution testing would require a mock JFrog server, which adds significant complexity for limited benefit. The script is straightforward bash that configures files and runs CLI commands. ## Testing ```bash cd registry/coder/modules/jfrog-oauth terraform test # Success! 12 passed, 0 failed. ``` _Generated with [mux](https://github.com/coder/mux)_
401 lines
10 KiB
HCL
401 lines
10 KiB
HCL
# Test for jfrog-oauth module
|
|
|
|
run "test_required_vars" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {}
|
|
}
|
|
|
|
# Mock external auth with valid access token for basic test
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
}
|
|
|
|
run "test_empty_access_token_fails" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {}
|
|
}
|
|
|
|
# Mock external auth with empty access token
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = ""
|
|
}
|
|
}
|
|
|
|
expect_failures = [
|
|
resource.coder_script.jfrog
|
|
]
|
|
}
|
|
|
|
run "test_valid_access_token_succeeds" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {}
|
|
}
|
|
|
|
# Mock external auth with valid access token
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
# Verify the script resource is created
|
|
assert {
|
|
condition = resource.coder_script.jfrog.agent_id == "test-agent-id"
|
|
error_message = "coder_script agent_id should match the input variable"
|
|
}
|
|
|
|
assert {
|
|
condition = resource.coder_script.jfrog.display_name == "jfrog"
|
|
error_message = "coder_script display_name should be 'jfrog'"
|
|
}
|
|
}
|
|
|
|
run "test_jfrog_url_validation" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "invalid-url"
|
|
package_managers = {}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
expect_failures = [
|
|
var.jfrog_url
|
|
]
|
|
}
|
|
|
|
run "test_username_field_validation" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
username_field = "invalid"
|
|
package_managers = {}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
expect_failures = [
|
|
var.username_field
|
|
]
|
|
}
|
|
|
|
run "test_with_npm_package_manager" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {
|
|
npm = ["global", "@foo:foo", "@bar:bar"]
|
|
}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
assert {
|
|
condition = resource.coder_script.jfrog.run_on_start == true
|
|
error_message = "coder_script should run on start"
|
|
}
|
|
|
|
# Verify npm configuration is in script
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "jf npmc --global --repo-resolve \"global\"")
|
|
error_message = "script should contain jf npmc command for npm"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "@foo:registry=https://example.jfrog.io/artifactory/api/npm/foo")
|
|
error_message = "script should contain scoped npm registry for @foo"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "@bar:registry=https://example.jfrog.io/artifactory/api/npm/bar")
|
|
error_message = "script should contain scoped npm registry for @bar"
|
|
}
|
|
}
|
|
|
|
run "test_configure_code_server" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
configure_code_server = true
|
|
package_managers = {}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
# When configure_code_server is true, env vars should be created
|
|
assert {
|
|
condition = length(resource.coder_env.jfrog_ide_url) == 1
|
|
error_message = "coder_env.jfrog_ide_url should be created when configure_code_server is true"
|
|
}
|
|
|
|
assert {
|
|
condition = length(resource.coder_env.jfrog_ide_access_token) == 1
|
|
error_message = "coder_env.jfrog_ide_access_token should be created when configure_code_server is true"
|
|
}
|
|
}
|
|
|
|
run "test_go_proxy_env" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {
|
|
go = ["foo", "bar", "baz"]
|
|
}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
# When go package manager is configured, GOPROXY env should be set
|
|
assert {
|
|
condition = length(resource.coder_env.goproxy) == 1
|
|
error_message = "coder_env.goproxy should be created when go package manager is configured"
|
|
}
|
|
|
|
# Verify GOPROXY contains all repos
|
|
assert {
|
|
condition = strcontains(resource.coder_env.goproxy[0].value, "example.jfrog.io/artifactory/api/go/foo")
|
|
error_message = "GOPROXY should contain foo repo"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_env.goproxy[0].value, "example.jfrog.io/artifactory/api/go/bar")
|
|
error_message = "GOPROXY should contain bar repo"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_env.goproxy[0].value, "example.jfrog.io/artifactory/api/go/baz")
|
|
error_message = "GOPROXY should contain baz repo"
|
|
}
|
|
|
|
# Verify script contains go configuration
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "jf goc --global --repo-resolve \"foo\"")
|
|
error_message = "script should contain jf goc command"
|
|
}
|
|
}
|
|
|
|
run "test_pypi_package_manager" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {
|
|
pypi = ["global", "foo", "bar"]
|
|
}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
# Verify pip configuration in script
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "jf pipc --global --repo-resolve \"global\"")
|
|
error_message = "script should contain jf pipc command"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "index-url = https://default:valid-token-value@example.jfrog.io/artifactory/api/pypi/global/simple")
|
|
error_message = "script should contain pip index-url configuration"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "extra-index-url")
|
|
error_message = "script should contain extra-index-url for additional repos"
|
|
}
|
|
}
|
|
|
|
run "test_docker_package_manager" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {
|
|
docker = ["foo.jfrog.io", "bar.jfrog.io", "baz.jfrog.io"]
|
|
}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
# Verify docker registration commands in script
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "register_docker \"foo.jfrog.io\"")
|
|
error_message = "script should contain register_docker for foo.jfrog.io"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "register_docker \"bar.jfrog.io\"")
|
|
error_message = "script should contain register_docker for bar.jfrog.io"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "register_docker \"baz.jfrog.io\"")
|
|
error_message = "script should contain register_docker for baz.jfrog.io"
|
|
}
|
|
}
|
|
|
|
run "test_conda_package_manager" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {
|
|
conda = ["conda-main", "conda-secondary", "conda-local"]
|
|
}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
# Verify conda configuration in script
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "channels:")
|
|
error_message = "script should contain conda channels configuration"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "example.jfrog.io/artifactory/api/conda/conda-main")
|
|
error_message = "script should contain conda-main channel"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "example.jfrog.io/artifactory/api/conda/conda-secondary")
|
|
error_message = "script should contain conda-secondary channel"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "example.jfrog.io/artifactory/api/conda/conda-local")
|
|
error_message = "script should contain conda-local channel"
|
|
}
|
|
}
|
|
|
|
run "test_maven_package_manager" {
|
|
command = plan
|
|
|
|
variables {
|
|
agent_id = "test-agent-id"
|
|
jfrog_url = "https://example.jfrog.io"
|
|
package_managers = {
|
|
maven = ["central", "snapshots", "local"]
|
|
}
|
|
}
|
|
|
|
override_data {
|
|
target = data.coder_external_auth.jfrog
|
|
values = {
|
|
access_token = "valid-token-value"
|
|
}
|
|
}
|
|
|
|
# Verify maven jf mvnc command
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "jf mvnc --global")
|
|
error_message = "script should contain jf mvnc command"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "--repo-resolve-releases \"central\"")
|
|
error_message = "script should contain repo-resolve-releases for central"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "--repo-resolve-snapshots \"central\"")
|
|
error_message = "script should contain repo-resolve-snapshots for central"
|
|
}
|
|
|
|
# Verify settings.xml content
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "<servers>")
|
|
error_message = "script should contain maven servers configuration"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "<id>central</id>")
|
|
error_message = "script should contain central server id"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "<id>snapshots</id>")
|
|
error_message = "script should contain snapshots server id"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "<id>local</id>")
|
|
error_message = "script should contain local server id"
|
|
}
|
|
|
|
assert {
|
|
condition = strcontains(resource.coder_script.jfrog.script, "<url>https://example.jfrog.io/artifactory/central</url>")
|
|
error_message = "script should contain central repository URL"
|
|
}
|
|
}
|