package service import ( "context" "strings" "testing" "oci-portal/internal/oci" ) func TestCreateInstanceValidation(t *testing.T) { valid := oci.CreateInstanceInput{ AvailabilityDomain: "AD-1", DisplayName: "vm1", Shape: "VM.Standard.A1.Flex", ImageID: "ocid1.image..a", SubnetID: "ocid1.subnet..s", } tests := []struct { name string mutate func(*oci.CreateInstanceInput) wantErr string }{ {name: "合法输入", mutate: func(*oci.CreateInstanceInput) {}}, {name: "缺名称", mutate: func(in *oci.CreateInstanceInput) { in.DisplayName = "" }, wantErr: "displayName"}, {name: "可用域可空(默认 ad-1)", mutate: func(in *oci.CreateInstanceInput) { in.AvailabilityDomain = "" }}, {name: "缺 shape", mutate: func(in *oci.CreateInstanceInput) { in.Shape = "" }, wantErr: "shape"}, {name: "缺启动源", mutate: func(in *oci.CreateInstanceInput) { in.ImageID = "" }, wantErr: "imageId or bootVolumeId"}, {name: "启动源互斥", mutate: func(in *oci.CreateInstanceInput) { in.BootVolumeID = "bv" }, wantErr: "mutually exclusive"}, {name: "子网可空(自动建网)", mutate: func(in *oci.CreateInstanceInput) { in.SubnetID = "" }}, {name: "密钥与密码互斥", mutate: func(in *oci.CreateInstanceInput) { in.SSHPublicKey = "ssh-ed25519 AAA" in.RootPassword = "pw" }, wantErr: "sshPublicKey and rootPassword"}, {name: "随机密码与密钥互斥", mutate: func(in *oci.CreateInstanceInput) { in.GenerateRootPassword = true in.SSHPublicKey = "ssh-ed25519 AAA" }, wantErr: "generateRootPassword"}, {name: "随机密码与固定密码互斥", mutate: func(in *oci.CreateInstanceInput) { in.GenerateRootPassword = true in.RootPassword = "pw" }, wantErr: "generateRootPassword"}, } svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}) cfg := importAliveConfig(t, svc) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { in := valid tt.mutate(&in) _, _, err := svc.CreateInstances(context.Background(), cfg.ID, in, 1) if tt.wantErr == "" { if err != nil { t.Errorf("CreateInstances: %v, want success", err) } return } if err == nil || !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("CreateInstances error = %v, want contains %q", err, tt.wantErr) } }) } } func TestCreateInstancesBatch(t *testing.T) { client := &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}} svc := newTestService(t, client) cfg := importAliveConfig(t, svc) in := oci.CreateInstanceInput{DisplayName: "vm", Shape: "VM.Standard.A1.Flex", ImageID: "img"} for _, count := range []int{0, 11} { if _, _, err := svc.CreateInstances(context.Background(), cfg.ID, in, count); err == nil { t.Errorf("count=%d: got nil error, want out-of-range failure", count) } } instances, failures, err := svc.CreateInstances(context.Background(), cfg.ID, in, 3) if err != nil { t.Fatalf("CreateInstances: %v", err) } if len(failures) != 0 { t.Errorf("failures = %v, want none", failures) } got := make([]string, 0, len(instances)) for _, inst := range instances { got = append(got, inst.DisplayName) } want := []string{"vm-1", "vm-2", "vm-3"} if len(got) != len(want) { t.Fatalf("created %d instances %v, want %v", len(got), got, want) } for i := range want { if got[i] != want[i] { t.Errorf("instance %d name = %q, want %q", i, got[i], want[i]) } } } func TestCreateInstancesGeneratedRootPassword(t *testing.T) { client := &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}} svc := newTestService(t, client) cfg := importAliveConfig(t, svc) in := oci.CreateInstanceInput{ DisplayName: "vm", Shape: "VM.Standard.A1.Flex", ImageID: "img", GenerateRootPassword: true, FreeformTags: map[string]string{"env": "prod"}, } if _, _, err := svc.CreateInstances(context.Background(), cfg.ID, in, 2); err != nil { t.Fatalf("CreateInstances: %v", err) } if len(client.launched) != 2 { t.Fatalf("launched %d instances, want 2", len(client.launched)) } pwds := map[string]bool{} for i, got := range client.launched { if got.RootPassword == "" { t.Errorf("instance %d: RootPassword empty, want generated", i) } if got.FreeformTags["RootPassword"] != got.RootPassword { t.Errorf("instance %d: tag RootPassword = %q, want %q", i, got.FreeformTags["RootPassword"], got.RootPassword) } if got.FreeformTags["env"] != "prod" { t.Errorf("instance %d: tag env = %q, want prod (原有标签须保留)", i, got.FreeformTags["env"]) } pwds[got.RootPassword] = true } if len(pwds) != 2 { t.Errorf("批量创建密码应各台独立, got %d 个不同密码", len(pwds)) } if in.FreeformTags["RootPassword"] != "" { t.Error("原始输入 FreeformTags 被修改, 应复制后再写入") } } func TestUpdateInstanceRequiresChange(t *testing.T) { svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}) cfg := importAliveConfig(t, svc) if _, err := svc.UpdateInstance(context.Background(), cfg.ID, "ocid1.instance..i", oci.UpdateInstanceInput{}); err == nil { t.Error("UpdateInstance with empty input: got nil error, want failure") } } func TestCreateConsoleConnectionValidation(t *testing.T) { svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}) cfg := importAliveConfig(t, svc) tests := []struct { name string key string }{ {name: "空公钥", key: ""}, {name: "非 RSA 公钥", key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA test"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if _, err := svc.CreateConsoleConnection(context.Background(), cfg.ID, "", "ocid1.instance..i", tt.key); err == nil { t.Error("CreateConsoleConnection: got nil error, want failure") } }) } } func TestDeleteInstanceIpv6RequiresAddress(t *testing.T) { svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}) cfg := importAliveConfig(t, svc) if err := svc.DeleteInstanceIpv6(context.Background(), cfg.ID, "", "ocid1.instance..i", ""); err == nil { t.Error("DeleteInstanceIpv6 with empty address: got nil error, want failure") } } func TestUpdateBootVolumeRequiresChange(t *testing.T) { svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}) cfg := importAliveConfig(t, svc) if _, err := svc.UpdateBootVolume(context.Background(), cfg.ID, "ocid1.bootvolume..b", oci.UpdateBootVolumeInput{}); err == nil { t.Error("UpdateBootVolume with empty input: got nil error, want failure") } }