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.
135 lines
4.3 KiB
Go
135 lines
4.3 KiB
Go
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")
|
|
}
|
|
}
|