A Terraform provider for Dokploy
Plugin-framework provider covering projects, environments, applications, Compose stacks, managed databases, domains, mounts, ports, redirects, basic auth, registries, SSH keys, certificates and backup destinations, over Dokploy's tRPC-over-REST API. The shim package exposes the provider to other Go modules, which is how pulumi-dokploy bridges it.
This commit is contained in:
134
internal/tfmap/tfmap_test.go
Normal file
134
internal/tfmap/tfmap_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package tfmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
type sample struct {
|
||||
ID types.String `tfsdk:"id" dokploy:"thingId,id"`
|
||||
Name types.String `tfsdk:"name" dokploy:"name"`
|
||||
Desc types.String `tfsdk:"description" dokploy:"description,nullable"`
|
||||
Networks types.List `tfsdk:"network_ids" dokploy:"networkIds"`
|
||||
Detach types.Bool `tfsdk:"detach" dokploy:"detach"`
|
||||
Replicas types.Int64 `tfsdk:"replicas" dokploy:"replicas"`
|
||||
Secret types.String `tfsdk:"secret" dokploy:"secret,noread"`
|
||||
ServerID types.String `tfsdk:"server_id" dokploy:"serverId,create"`
|
||||
Status types.String `tfsdk:"status" dokploy:"status,ro"`
|
||||
}
|
||||
|
||||
// A JSON null in the response must produce a null attribute, never leave the
|
||||
// attribute unknown: Terraform rejects unknown values after apply.
|
||||
func TestFromAPIConvertsJSONNullToNullAttribute(t *testing.T) {
|
||||
model := &sample{
|
||||
Networks: types.ListUnknown(types.StringType),
|
||||
Detach: types.BoolUnknown(),
|
||||
Replicas: types.Int64Unknown(),
|
||||
}
|
||||
|
||||
raw := json.RawMessage(`{"networkIds":null,"detach":null,"replicas":null}`)
|
||||
if err := FromAPI(raw, model); err != nil {
|
||||
t.Fatalf("FromAPI returned an error: %v", err)
|
||||
}
|
||||
|
||||
if model.Networks.IsUnknown() {
|
||||
t.Error("networkIds stayed unknown; want null")
|
||||
}
|
||||
if !model.Networks.IsNull() {
|
||||
t.Errorf("networkIds = %v; want null", model.Networks)
|
||||
}
|
||||
if model.Detach.IsUnknown() || !model.Detach.IsNull() {
|
||||
t.Errorf("detach = %v; want null", model.Detach)
|
||||
}
|
||||
if model.Replicas.IsUnknown() || !model.Replicas.IsNull() {
|
||||
t.Errorf("replicas = %v; want null", model.Replicas)
|
||||
}
|
||||
}
|
||||
|
||||
// Keys absent from the response must not clobber existing values, but they
|
||||
// must also not survive as unknown once NullifyUnknown runs.
|
||||
func TestFromAPILeavesAbsentKeysUntouched(t *testing.T) {
|
||||
model := &sample{
|
||||
Name: types.StringValue("keep-me"),
|
||||
Networks: types.ListUnknown(types.StringType),
|
||||
}
|
||||
|
||||
if err := FromAPI(json.RawMessage(`{"description":"hi"}`), model); err != nil {
|
||||
t.Fatalf("FromAPI returned an error: %v", err)
|
||||
}
|
||||
|
||||
if got := model.Name.ValueString(); got != "keep-me" {
|
||||
t.Errorf("name = %q; want %q", got, "keep-me")
|
||||
}
|
||||
if got := model.Desc.ValueString(); got != "hi" {
|
||||
t.Errorf("description = %q; want %q", got, "hi")
|
||||
}
|
||||
if !model.Networks.IsUnknown() {
|
||||
t.Error("networkIds should still be unknown before NullifyUnknown runs")
|
||||
}
|
||||
|
||||
if err := NullifyUnknown(model); err != nil {
|
||||
t.Fatalf("NullifyUnknown returned an error: %v", err)
|
||||
}
|
||||
if model.Networks.IsUnknown() {
|
||||
t.Error("networkIds stayed unknown after NullifyUnknown")
|
||||
}
|
||||
}
|
||||
|
||||
// noread fields must never be overwritten by the API response.
|
||||
func TestFromAPISkipsNoReadFields(t *testing.T) {
|
||||
model := &sample{Secret: types.StringValue("plaintext")}
|
||||
|
||||
if err := FromAPI(json.RawMessage(`{"secret":"$2b$10$hashed"}`), model); err != nil {
|
||||
t.Fatalf("FromAPI returned an error: %v", err)
|
||||
}
|
||||
if got := model.Secret.ValueString(); got != "plaintext" {
|
||||
t.Errorf("secret = %q; want the configured value to survive", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToAPIPhaseSelection(t *testing.T) {
|
||||
model := &sample{
|
||||
ID: types.StringValue("abc123"),
|
||||
Name: types.StringValue("thing"),
|
||||
Desc: types.StringNull(),
|
||||
ServerID: types.StringValue("srv1"),
|
||||
Status: types.StringValue("running"),
|
||||
Networks: types.ListUnknown(types.StringType),
|
||||
}
|
||||
|
||||
create, err := ToAPI(model, PhaseCreate)
|
||||
if err != nil {
|
||||
t.Fatalf("ToAPI(create) returned an error: %v", err)
|
||||
}
|
||||
if _, ok := create["thingId"]; ok {
|
||||
t.Error("create body must not carry the server-generated ID")
|
||||
}
|
||||
if create["serverId"] != "srv1" {
|
||||
t.Errorf("create body serverId = %v; want srv1", create["serverId"])
|
||||
}
|
||||
if _, ok := create["status"]; ok {
|
||||
t.Error("read-only fields must never be sent")
|
||||
}
|
||||
if _, ok := create["networkIds"]; ok {
|
||||
t.Error("unknown values must never be sent")
|
||||
}
|
||||
// description is null but tagged nullable, so it is sent explicitly.
|
||||
value, ok := create["description"]
|
||||
if !ok || value != nil {
|
||||
t.Errorf("description = %v (present %v); want an explicit null", value, ok)
|
||||
}
|
||||
|
||||
update, err := ToAPI(model, PhaseUpdate)
|
||||
if err != nil {
|
||||
t.Fatalf("ToAPI(update) returned an error: %v", err)
|
||||
}
|
||||
if update["thingId"] != "abc123" {
|
||||
t.Errorf("update body thingId = %v; want abc123", update["thingId"])
|
||||
}
|
||||
if _, ok := update["serverId"]; ok {
|
||||
t.Error("create-only fields must not be sent on update")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user