Skip to content

Commit 3535d09

Browse files
authored
[AV-128452] Migrate to claude code (#563)
1 parent 191d3f3 commit 3535d09

7 files changed

Lines changed: 668 additions & 0 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
---
2+
name: tf-acceptance-test-gen
3+
description: Generate acceptance tests for a named Terraform resource or data source in the Couchbase Capella provider.
4+
---
5+
6+
# Terraform Acceptance Test Generator
7+
8+
Acceptance tests live in `acceptance_tests/` and are named `<feature>_acceptance_test.go`.
9+
10+
## Test structure
11+
12+
Every resource test follows this pattern:
13+
14+
```go
15+
func TestAcc<Feature>Resource(t *testing.T) {
16+
resourceName := randomStringWithPrefix("tf_acc_<feature>_")
17+
resourceReference := "couchbase-capella_<type_name>." + resourceName
18+
19+
resource.ParallelTest(t, resource.TestCase{
20+
ProtoV6ProviderFactories: globalProtoV6ProviderFactory,
21+
Steps: []resource.TestStep{
22+
// Create and Read
23+
{
24+
Config: testAcc<Feature>ResourceConfig(resourceName, <values...>),
25+
Check: resource.ComposeAggregateTestCheckFunc(
26+
testAccExists<Feature>Resource(t, resourceReference),
27+
resource.TestCheckResourceAttr(resourceReference, "organization_id", globalOrgId),
28+
resource.TestCheckResourceAttr(resourceReference, "<required_field>", "<expected_value>"),
29+
resource.TestCheckResourceAttrSet(resourceReference, "id"),
30+
resource.TestCheckResourceAttrSet(resourceReference, "<computed_field>"),
31+
),
32+
},
33+
// ImportState
34+
{
35+
ResourceName: resourceReference,
36+
ImportStateIdFunc: generate<Feature>ImportIdForResource(resourceReference),
37+
ImportState: true,
38+
},
39+
// Update (omit if resource has no updatable fields)
40+
{
41+
Config: testAcc<Feature>ResourceConfig(resourceName, <updated_values...>),
42+
Check: resource.ComposeAggregateTestCheckFunc(
43+
resource.TestCheckResourceAttr(resourceReference, "<updated_field>", "<new_value>"),
44+
),
45+
},
46+
},
47+
// Delete testing automatically occurs in TestCase
48+
})
49+
}
50+
```
51+
52+
- Always use `resource.ParallelTest()`.
53+
- Always include an ImportState step.
54+
- Use `TestCheckResourceAttr` for fields with known values; `TestCheckResourceAttrSet` for computed fields.
55+
- Never hardcode IDs — use `globalOrgId`, `globalProjectId`, `globalClusterId`, `globalBucketId`, `globalAppServiceId`.
56+
57+
## Writing meaningful assertions
58+
59+
The goal of each assertion is to guard a specific behaviour, not to confirm the test ran. Apply these rules:
60+
61+
**Assert values, not just presence.** Every field you set in the config must be asserted with `TestCheckResourceAttr` using the exact value you set. `TestCheckResourceAttrSet` only proves a field is non-empty — it does not prove the provider stored the right value. Reserve `TestCheckResourceAttrSet` for fields whose values are genuinely unknowable at test time (server-generated IDs, timestamps, status fields set by the API).
62+
63+
**After an update, assert the full state.** The update step must check every field — not just the one that changed. This catches regressions where updating field A silently resets field B to its default.
64+
65+
**Error tests must be specific.** The `ExpectError` regex must match a substring of the real error message for that specific invalid input, not a generic pattern like `"error"` or `"invalid"`. Read the resource's `Create` implementation and `internal/errors/` to find the actual message. A test that accepts any error is not guarding the right behaviour.
66+
67+
**Optional fields need their own test.** If a resource has optional fields that change API behaviour (not just metadata), write a dedicated test that sets those fields and asserts the resulting state. Don't rely on the happy-path test covering them incidentally.
68+
69+
**Default values must be verified.** If a field has a default, write a test that omits it and asserts the default value appears in state. This confirms the provider applies and reads back the default correctly.
70+
71+
## Config builder
72+
73+
```go
74+
func testAcc<Feature>ResourceConfig(resourceName string, field1 <type>) string {
75+
return fmt.Sprintf(`
76+
%[1]s
77+
78+
resource "couchbase-capella_<type_name>" "%[2]s" {
79+
organization_id = "%[3]s"
80+
project_id = "%[4]s"
81+
cluster_id = "%[5]s"
82+
<field> = <format_verb>
83+
}
84+
`, globalProviderBlock, resourceName, globalOrgId, globalProjectId, globalClusterId, field1)
85+
}
86+
```
87+
88+
Always start with `%[1]s` for `globalProviderBlock`. Number format verbs sequentially.
89+
90+
## Import ID function
91+
92+
Read the `ImportState()` function in `internal/resources/<feature>.go` to find the exact composite key format, then implement:
93+
94+
```go
95+
func generate<Feature>ImportIdForResource(resourceReference string) resource.ImportStateIdFunc {
96+
return func(state *terraform.State) (string, error) {
97+
var rawState map[string]string
98+
for _, m := range state.Modules {
99+
if len(m.Resources) > 0 {
100+
if v, ok := m.Resources[resourceReference]; ok {
101+
rawState = v.Primary.Attributes
102+
}
103+
}
104+
}
105+
return fmt.Sprintf(
106+
"id=%s,cluster_id=%s,project_id=%s,organization_id=%s",
107+
rawState["id"], rawState["cluster_id"], rawState["project_id"], rawState["organization_id"],
108+
), nil
109+
}
110+
}
111+
```
112+
113+
## Error-case test
114+
115+
```go
116+
func TestAcc<Feature>ResourceInvalid<Field>(t *testing.T) {
117+
resourceName := randomStringWithPrefix("tf_acc_<feature>_")
118+
resource.ParallelTest(t, resource.TestCase{
119+
ProtoV6ProviderFactories: globalProtoV6ProviderFactory,
120+
Steps: []resource.TestStep{
121+
{
122+
Config: testAcc<Feature>ResourceConfig(resourceName, <invalid_value>),
123+
ExpectError: regexp.MustCompile("<substring of expected error>"),
124+
},
125+
},
126+
})
127+
}
128+
```
129+
130+
## Data source tests
131+
132+
- Test function names for datasources should use the pattern `TestAccDatasource<Feature>`.
133+
For example: `func TestAccDatasourceCluster(t *testing.T)`
134+
- Omit the ImportState and Update steps.
135+
136+
## Verifying and running the tests
137+
138+
At session start the environment check reports which variables are set. If all three required variables (`TF_VAR_host`, `TF_VAR_auth_token`, `TF_VAR_organization_id`) are present, run only the tests for the resource you just wrote:
139+
140+
```bash
141+
TF_ACC=1 go test -timeout=60m -v ./acceptance_tests/ -run TestAcc<Feature>
142+
```
143+
144+
When credentials are not available the tests cannot be executed. The only verification possible is that the file compiles:
145+
146+
```bash
147+
go test -c ./acceptance_tests
148+
```
149+
150+
If compile errors are found fix and recompile to verify no errors.
151+
152+
## Reference examples
153+
154+
| Scenario | File |
155+
|---|---|
156+
| Simple resource with update | `acceptance_tests/snapshot_backup_acceptance_test.go` |
157+
| Resource with many optional fields | `acceptance_tests/apikey_acceptance_test.go` |
158+
| Complex nested resource | `acceptance_tests/cluster_acceptance_test.go` |

.claude/skills/tf-bugfix/SKILL.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
name: tf-bugfix
3+
description: Diagnose and fix bugs in the Terraform Capella provider with acceptance tests.
4+
---
5+
6+
# Terraform Bug Fix
7+
8+
## Instructions
9+
10+
0. Read the Jira ticket or GitHub issue provided by the user.
11+
Summarize: what is the bug, what is the expected behavior,
12+
and what is the actual behavior.
13+
14+
1. Search the codebase for the function, file, or code path
15+
mentioned in the bug report. Read the relevant source files
16+
to understand the current behavior.
17+
18+
2. Identify the root cause. Explain why the current code
19+
produces the bug (e.g., missing bounds check, nil pointer,
20+
incorrect logic).
21+
22+
3. Propose a robust fix that addresses the root cause.
23+
24+
4. Add an acceptance test in `acceptance_tests/` to
25+
validate the fix end-to-end:
26+
- Follow existing patterns: use `resource.ParallelTest()`,
27+
`globalProtoV6ProviderFactory`, and helper functions like
28+
`randomStringWithPrefix`.
29+
- Name the test in this format `TestAcc<Feature>_AV_XXXXX` e.g. `TestAccProject_AV_12345`.
30+
31+
5. Do not run the acceptance test.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
name: tf-datasource-gen
3+
description: generate terraform datasources based on openapi spec.
4+
---
5+
6+
# Terraform Datasource Generator
7+
8+
## Instructions
9+
10+
0. First inspect the repo for existing datasource code, schema files, api structs,
11+
provider registrations, and acceptance tests for the feature. For each step 1 through
12+
12 if the datasource already satisfies the user request skip the step
13+
completely, make no edits and proceed to the next step. Do not make any minor edits or fixes
14+
to existing code if the datasource already satisfies the user request.
15+
16+
For example:
17+
- If api structs exist, skip creating them and use the existing structs
18+
- If a datasource already exists, skip recreating it unless explicitly asked to edit it.
19+
- If an acceptance test already exists, skip creating a duplicate and extend coverage only if asked.
20+
21+
1. datasource code should be in internal/datasources/
22+
2. schema for datasource should be in its own file with format <feature>_schema.go
23+
24+
add validation for organization_id, project_id and cluster_id if present. for example with organization_id
25+
26+
```
27+
capellaschema.AddAttr(attrs, "organization_id", snapshotBackupBuilder, requiredStringWithValidator())
28+
29+
func requiredStringWithValidator() *schema.StringAttribute {
30+
return &schema.StringAttribute{
31+
Required: true,
32+
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
33+
}
34+
}
35+
```
36+
37+
3. implement one or two datasources depending on the spec provided.
38+
39+
- the first is to get a specific resource. use the get endpoint. if there is no get endpoint then skip this implementation.
40+
for example if the feature is Buckets then need bucket.go to get a specific bucket.
41+
42+
- the second datasource is to list all resources. use the list endpoint. if there is no list endpoint then skip this implementation.
43+
the file name should have a plural resource name.
44+
for example if the feature is Buckets then need buckets.go to list all buckets.
45+
46+
4. create struct with feature name that embeds Data struct. for example if the feature is Buckets then need this struct
47+
48+
```
49+
type Buckets struct {
50+
*providerschema.Data
51+
}
52+
```
53+
54+
5. need New function. for example if feature is Buckets then need this function
55+
56+
```
57+
func NewBuckets() datasource.DataSource {
58+
return &Buckets{}
59+
}
60+
```
61+
62+
6. type should implement interfaces datasource.DataSource and datasource.DataSourceWithConfigure.
63+
must use type conversion of nil to assert that the type implements the interfaces.
64+
65+
for example for Buckets
66+
67+
```
68+
var (
69+
_ datasource.DataSource = (*Buckets)(nil)
70+
_ datasource.DataSourceWithConfigure = (*Buckets)(nil)
71+
)
72+
```
73+
74+
7. need Metadata function. for example with Buckets
75+
76+
```
77+
func (d *Buckets) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
78+
resp.TypeName = req.ProviderTypeName + "_buckets"
79+
}
80+
```
81+
82+
8. need Configure function. for example with Buckets
83+
84+
```
85+
func (d *Buckets) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
86+
if req.ProviderData == nil {
87+
return
88+
}
89+
90+
data, ok := req.ProviderData.(*providerschema.Data)
91+
if !ok {
92+
resp.Diagnostics.AddError(
93+
"Unexpected Data Source Configure Type",
94+
fmt.Sprintf("Expected *providerschema.Data, got: %T. Please report this issue to the provider developers.", req.ProviderData),
95+
)
96+
97+
return
98+
}
99+
100+
d.Data = data
101+
}
102+
```
103+
104+
9. generate necessary structs to handle API response. put structs in internal/api/
105+
use ClientV1 struct to make API calls with retry logic. for example:
106+
107+
```
108+
response, err := s.ClientV1.ExecuteWithRetry
109+
```
110+
111+
10. register the datasource in `internal/provider/provider.go` in func `(p *capellaProvider) DataSources`
112+
113+
for example with buckets need `datasources.NewBuckets,`
114+
115+
11. create acceptance tests for both datasources in acceptance_tests/ with format <feature>_test.go.
116+
for example if feature is Buckets then need buckets_test.go
117+
118+
12. acceptance tests should run in parallel. that is use resource.ParallelTest()
119+
120+

0 commit comments

Comments
 (0)