registry/registry/coder/modules/jfrog-oauth/jfrog-oauth.tftest.hcl
Atif Ali d21db0d220
fix(jfrog-oauth): fail when access_token is empty (#574)
## 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)_
2025-12-02 13:17:39 -06:00

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"
}
}