package oci import ( "encoding/base64" "strings" "testing" "github.com/oracle/oci-go-sdk/v65/core" ) func TestShellSingleQuote(t *testing.T) { tests := []struct { name string in string want string }{ {name: "普通", in: "Passw0rd", want: "'Passw0rd'"}, {name: "含单引号", in: "a'b", want: `'a'\''b'`}, {name: "含空格特殊符", in: "p @ss!", want: "'p @ss!'"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := shellSingleQuote(tt.in); got != tt.want { t.Errorf("shellSingleQuote(%q) = %q, want %q", tt.in, got, tt.want) } }) } } func TestRootPasswordUserData(t *testing.T) { encoded := rootPasswordUserData("Secret123") raw, err := base64.StdEncoding.DecodeString(encoded) if err != nil { t.Fatalf("result is not base64: %v", err) } script := string(raw) for _, want := range []string{ "#!/bin/bash", "echo root:'Secret123' | chpasswd", "PermitRootLogin yes", "PasswordAuthentication yes", "systemctl restart sshd", } { if !strings.Contains(script, want) { t.Errorf("script missing %q\n---\n%s", want, script) } } } func TestBuildInstanceMetadataRootPasswordOverridesUserData(t *testing.T) { md := buildInstanceMetadata(CreateInstanceInput{ RootPassword: "pw", UserData: "ZXhwbGljaXQ=", // "explicit" }) decoded, _ := base64.StdEncoding.DecodeString(md["user_data"]) if !strings.Contains(string(decoded), "chpasswd") { t.Errorf("user_data = %q, want root password script (not explicit userData)", md["user_data"]) } } func TestBuildInstanceSourceVpusPerGB(t *testing.T) { src := buildInstanceSource(CreateInstanceInput{ImageID: "img", BootVolumeSizeGBs: 100, BootVolumeVpusPerGB: 20}) img, ok := src.(core.InstanceSourceViaImageDetails) if !ok { t.Fatalf("source type = %T, want image", src) } if img.BootVolumeVpusPerGB == nil || *img.BootVolumeVpusPerGB != 20 { t.Errorf("BootVolumeVpusPerGB = %v, want 20", img.BootVolumeVpusPerGB) } } func TestBuildInstanceSource(t *testing.T) { tests := []struct { name string in CreateInstanceInput wantType string }{ { name: "从镜像启动", in: CreateInstanceInput{ImageID: "ocid1.image..a", BootVolumeSizeGBs: 100}, wantType: "image", }, { name: "从引导卷启动", in: CreateInstanceInput{BootVolumeID: "ocid1.bootvolume..b"}, wantType: "bootVolume", }, { name: "引导卷优先于镜像", in: CreateInstanceInput{ImageID: "ocid1.image..a", BootVolumeID: "ocid1.bootvolume..b"}, wantType: "bootVolume", }, { name: "两者皆无返回 nil", in: CreateInstanceInput{}, wantType: "nil", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { src := buildInstanceSource(tt.in) switch tt.wantType { case "image": img, ok := src.(core.InstanceSourceViaImageDetails) if !ok { t.Fatalf("source type = %T, want InstanceSourceViaImageDetails", src) } if got, want := *img.BootVolumeSizeInGBs, tt.in.BootVolumeSizeGBs; got != want { t.Errorf("BootVolumeSizeInGBs = %d, want %d", got, want) } case "bootVolume": if _, ok := src.(core.InstanceSourceViaBootVolumeDetails); !ok { t.Fatalf("source type = %T, want InstanceSourceViaBootVolumeDetails", src) } case "nil": if src != nil { t.Fatalf("source = %v, want nil", src) } } }) } } func TestBuildLaunchDetails(t *testing.T) { in := CreateInstanceInput{ AvailabilityDomain: "AD-1", DisplayName: "vm1", Shape: "VM.Standard.A1.Flex", Ocpus: 2, MemoryInGBs: 12, ImageID: "ocid1.image..a", SubnetID: "ocid1.subnet..s", AssignPublicIP: true, AssignIpv6: true, SSHPublicKey: "ssh-ed25519 AAA", } details, err := buildLaunchDetails("ocid1.tenancy..t", in) if err != nil { t.Fatalf("buildLaunchDetails: %v", err) } if got, want := *details.ShapeConfig.Ocpus, float32(2); got != want { t.Errorf("Ocpus = %v, want %v", got, want) } if got, want := *details.ShapeConfig.MemoryInGBs, float32(12); got != want { t.Errorf("MemoryInGBs = %v, want %v", got, want) } if got, want := *details.CreateVnicDetails.SubnetId, in.SubnetID; got != want { t.Errorf("SubnetId = %v, want %v", got, want) } if details.CreateVnicDetails.AssignIpv6Ip == nil || !*details.CreateVnicDetails.AssignIpv6Ip { t.Error("AssignIpv6Ip not set, want true") } if got, want := details.Metadata["ssh_authorized_keys"], in.SSHPublicKey; got != want { t.Errorf("ssh_authorized_keys = %q, want %q", got, want) } } func TestBuildLaunchDetailsRequiresSource(t *testing.T) { _, err := buildLaunchDetails("ocid1.tenancy..t", CreateInstanceInput{ AvailabilityDomain: "AD-1", DisplayName: "vm1", Shape: "shape", SubnetID: "sub", }) if err == nil { t.Error("buildLaunchDetails without source: got nil error, want failure") } } func TestBuildLaunchDetailsNoShapeConfig(t *testing.T) { details, err := buildLaunchDetails("t", CreateInstanceInput{ AvailabilityDomain: "AD-1", DisplayName: "vm1", Shape: "VM.Standard.E2.1.Micro", ImageID: "img", SubnetID: "sub", }) if err != nil { t.Fatalf("buildLaunchDetails: %v", err) } if details.ShapeConfig != nil { t.Errorf("ShapeConfig = %+v, want nil for fixed shape", details.ShapeConfig) } if details.Metadata != nil { t.Errorf("Metadata = %v, want nil when no key and user data", details.Metadata) } }