Model apps as a set instead of an ordered list #2

Merged
benvin merged 1 commits from benvin/apps-set into main 2026-08-23 00:08:58 +10:00
2 changed files with 32 additions and 14 deletions
@@ -25,7 +25,7 @@ type secretBackendRoleResource struct {
type secretBackendRoleModel struct {
Backend types.String `tfsdk:"backend"`
Name types.String `tfsdk:"name"`
Apps types.List `tfsdk:"apps"`
Apps types.Set `tfsdk:"apps"`
TTL types.Int64 `tfsdk:"ttl"`
MaxTTL types.Int64 `tfsdk:"max_ttl"`
}
@@ -56,7 +56,7 @@ func (r *secretBackendRoleResource) Schema(_ context.Context, _ resource.SchemaR
stringplanmodifier.RequiresReplace(),
},
},
"apps": schema.ListAttribute{
"apps": schema.SetAttribute{
Description: "arr apps a generated key may access (subset of sonarr, radarr, prowlarr).",
ElementType: types.StringType,
Required: true,
@@ -212,9 +212,9 @@ func applyRoleData(m *secretBackendRoleModel, role map[string]interface{}) diag.
var diags diag.Diagnostics
apps := toStringSlice(role["apps"])
appList, appDiags := types.ListValueFrom(context.Background(), types.StringType, apps)
appSet, appDiags := types.SetValueFrom(context.Background(), types.StringType, apps)
diags.Append(appDiags...)
m.Apps = appList
m.Apps = appSet
if n, ok := toInt64(role["ttl"]); ok && n != 0 {
m.TTL = types.Int64Value(n)
@@ -8,18 +8,18 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)
func listOf(t *testing.T, vals ...string) types.List {
func setOf(t *testing.T, vals ...string) types.Set {
t.Helper()
l, diags := types.ListValueFrom(context.Background(), types.StringType, vals)
s, diags := types.SetValueFrom(context.Background(), types.StringType, vals)
if diags.HasError() {
t.Fatalf("building list: %v", diags)
t.Fatalf("building set: %v", diags)
}
return l
return s
}
func TestRoleDataOmitsUnsetTTLs(t *testing.T) {
m := secretBackendRoleModel{
Apps: listOf(t, "sonarr", "radarr", "prowlarr"),
Apps: setOf(t, "sonarr", "radarr", "prowlarr"),
TTL: types.Int64Null(),
MaxTTL: types.Int64Null(),
}
@@ -41,7 +41,7 @@ func TestRoleDataOmitsUnsetTTLs(t *testing.T) {
func TestRoleDataIncludesTTLs(t *testing.T) {
m := secretBackendRoleModel{
Apps: listOf(t, "prowlarr"),
Apps: setOf(t, "prowlarr"),
TTL: types.Int64Value(60),
MaxTTL: types.Int64Value(86400),
}
@@ -70,16 +70,34 @@ func TestApplyRoleDataMapsEngineResponse(t *testing.T) {
if diags := applyRoleData(&m, role); diags.HasError() {
t.Fatalf("applyRoleData: %v", diags)
}
var apps []string
m.Apps.ElementsAs(context.Background(), &apps, false)
if len(apps) != 3 || apps[2] != "prowlarr" {
t.Errorf("apps = %v, want [sonarr radarr prowlarr]", apps)
if !m.Apps.Equal(setOf(t, "sonarr", "radarr", "prowlarr")) {
t.Errorf("apps = %v, want {sonarr radarr prowlarr}", m.Apps)
}
if m.TTL.ValueInt64() != 60 || m.MaxTTL.ValueInt64() != 86400 {
t.Errorf("ttl/max_ttl = %d/%d, want 60/86400", m.TTL.ValueInt64(), m.MaxTTL.ValueInt64())
}
}
func TestApplyRoleDataOrderInsensitive(t *testing.T) {
// The engine returns apps alphabetically sorted regardless of the order
// they were written in; the read-back must still equal the config value.
configApps := setOf(t, "sonarr", "prowlarr", "radarr")
role := map[string]interface{}{
"apps": []interface{}{"prowlarr", "radarr", "sonarr"},
}
m := secretBackendRoleModel{
Apps: configApps,
TTL: types.Int64Null(),
MaxTTL: types.Int64Null(),
}
if diags := applyRoleData(&m, role); diags.HasError() {
t.Fatalf("applyRoleData: %v", diags)
}
if !m.Apps.Equal(configApps) {
t.Errorf("apps after read-back = %v, not equal to config value %v", m.Apps, configApps)
}
}
func TestApplyRoleDataZeroTTLLeavesNull(t *testing.T) {
// The engine returns 0 for an unset TTL, which must not clobber the null
// model value into a spurious 0.