Files
cairnobs/terraform/internal/provider/dashboard_resource_test.go
T
jcoffey-dev 49dd050689 Start the Terraform provider: sentry_dashboard, the first resource
CLAUDE.md names the Terraform provider a first-class deliverable
alongside sentryctl ("CLI and Terraform provider are first-class, not
afterthoughts"), but no phase before this one had actually built any of
it -- no terraform/ directory existed. This is a first slice, not a
finished provider: one resource, scoped and confirmed with the project
owner before starting (a new pinned external dependency and an
architectural decision not covered in /docs/architecture.md are both
things CLAUDE.md's own "When in doubt" section says to ask about).

New Go module (terraform/, github.com/sentry/sentry/terraform) built on
HashiCorp's terraform-plugin-framework -- the actively-developed
library, not the legacy SDKv2, since there's no existing provider code
to migrate and no reason to start new on the framework HashiCorp itself
steers people away from.

internal/provider/client.go talks the exact same JSON contract
sentryctl's "dashboards apply" and web's Export JSON button already use
against api/dashboards.Handler (POST/GET/PUT/DELETE /dashboards[/{id}]) --
cli/README.md already named this "the seed of a future Terraform
provider: one JSON contract, multiple callers," this is that third
caller, not a new contract invented for Terraform's sake.

sentry_dashboard's schema deliberately leaves default_earliest/
default_latest Optional+Computed with no Terraform-side static default,
even though the API defaults them to "-1h"/"now" when empty -- letting
the API stay the one source of truth for what "unset" means rather than
duplicating that default in two places that could drift. tenant_id is
Computed-only, matching api/dashboards.Handler's own tenantID() doc
comment that a client-supplied value is always overridden server-side.

Panels are not modeled by this resource -- a genuinely separate resource
shape (own lifecycle, own endpoints, own validation needs), scoped out
deliberately, not an oversight. Alert rules, notification targets, and
tenant/RBAC resources are the same: real, disclosed future work, not
attempted in this pass. See terraform/README.md for the full accounting.

Verified: client_test.go runs real HTTP round trips against httptest.
Server (request construction, response parsing, the 404-vs-other-error
distinction Read/Delete need for Terraform's out-of-band-deletion
convention) -- same pattern cli/cmd/sentryctl's own tests already use
against the same api/dashboards endpoints. provider_test.go validates
both schemas are internally well-formed without needing a Terraform
binary. dashboard_resource_test.go's TestAccDashboardResource_basic is a
real acceptance test (terraform-plugin-testing), skip-gated by TF_ACC=1
per that framework's own convention -- even with TF_ACC set it would
still need a live api service (Postgres+ClickHouse) to apply against,
which this environment has no Docker access to bring up, so it has not
actually run here, same disclosed gap as every other live-infra test in
this repo.
2026-08-15 00:06:26 -07:00

84 lines
3.2 KiB
Go

package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
// testAccProtoV6ProviderFactories wires this package's own provider
// implementation into terraform-plugin-testing's acceptance-test
// runner -- HashiCorp's standard pattern, one factory reused by every
// acceptance test in this package.
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"sentry": providerserver.NewProtocol6WithError(New("test")()),
}
// The acceptance test below is gated the same way every other live-
// infrastructure test in this repo is (skip-gated, not deleted or
// faked) -- terraform-plugin-testing's own resource.Test already skips
// unless TF_ACC=1 is set, the framework's standard convention, and it
// additionally needs a real running api service (Docker/Postgres this
// environment doesn't have access to -- see /docs/phase-4-runbook.md's
// "Verification status" section for the same disclosed gap everywhere
// else in this codebase). "The test exists and is correct Go" is not
// the same claim as "this resource has been applied for real," per this
// repo's established honesty discipline.
func TestAccDashboardResource_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: `
provider "sentry" {
endpoint = "http://localhost:8080"
}
resource "sentry_dashboard" "test" {
name = "Acceptance Test Dashboard"
description = "created by TestAccDashboardResource_basic"
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("sentry_dashboard.test", "name", "Acceptance Test Dashboard"),
resource.TestCheckResourceAttr("sentry_dashboard.test", "description", "created by TestAccDashboardResource_basic"),
resource.TestCheckResourceAttrSet("sentry_dashboard.test", "id"),
resource.TestCheckResourceAttrSet("sentry_dashboard.test", "tenant_id"),
// Left unset in config -- must come back as the
// server's own defaults (store.go: "-1h"/"now"), not
// an empty string, proving the Optional+Computed
// schema round-trips the server's default rather
// than fighting it with a Terraform-side one.
resource.TestCheckResourceAttr("sentry_dashboard.test", "default_earliest", "-1h"),
resource.TestCheckResourceAttr("sentry_dashboard.test", "default_latest", "now"),
),
},
{
// Update: name change should apply in place, not
// replace (no RequiresReplace plan modifier on name).
Config: `
provider "sentry" {
endpoint = "http://localhost:8080"
}
resource "sentry_dashboard" "test" {
name = "Renamed Dashboard"
description = "created by TestAccDashboardResource_basic"
}
`,
Check: resource.TestCheckResourceAttr("sentry_dashboard.test", "name", "Renamed Dashboard"),
},
{
// Import: re-reads by ID alone and must match what's in
// state, proving Read()'s server round trip agrees with
// what Create()/Update() last wrote.
ResourceName: "sentry_dashboard.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}