package proxy import ( "context" "errors" "net/netip" "testing" "github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type mockStore struct { machines []*pb.MachineInfo err error } func (s *mockStore) ListMachines(_ context.Context) ([]*pb.MachineInfo, error) { if s.err != nil { return nil, s.err } return s.machines, nil } func machineInfo(id, name, ip string) *pb.MachineInfo { return &pb.MachineInfo{ Id: id, Name: name, Network: &pb.NetworkConfig{ ManagementIp: pb.NewIP(netip.MustParseAddr(ip)), }, } } func TestCorrosionMapper_MapMachines(t *testing.T) { ctx := context.Background() machines := []*pb.MachineInfo{ machineInfo("id-1", "machine-a", "fd00::1"), machineInfo("id-2", "machine-b", "fd00::2"), } tests := []struct { name string store *mockStore input []string want []MachineTarget wantErr bool errMsg string }{ { name: "wildcard returns all machines", store: &mockStore{machines: machines}, input: []string{"*"}, want: []MachineTarget{ {ID: "id-1", Name: "machine-a", Addr: "fd00::1"}, {ID: "id-2", Name: "machine-b", Addr: "fd00::2"}, }, }, { name: "single name", store: &mockStore{machines: machines}, input: []string{"machine-a"}, want: []MachineTarget{ {ID: "id-1", Name: "machine-a", Addr: "fd00::1"}, }, }, { name: "single id", store: &mockStore{machines: machines}, input: []string{"id-2"}, want: []MachineTarget{ {ID: "id-2", Name: "machine-b", Addr: "fd00::2"}, }, }, { name: "multiple mixed", store: &mockStore{machines: machines}, input: []string{"machine-a", "id-2"}, want: []MachineTarget{ {ID: "id-1", Name: "machine-a", Addr: "fd00::1"}, {ID: "id-2", Name: "machine-b", Addr: "fd00::2"}, }, }, { name: "deduplicates repeated inputs", store: &mockStore{machines: machines}, input: []string{"machine-a", "machine-a"}, want: []MachineTarget{ {ID: "id-1", Name: "machine-a", Addr: "fd00::1"}, }, }, { name: "deduplicates name and id for same machine", store: &mockStore{machines: machines}, input: []string{"machine-a", "id-1"}, want: []MachineTarget{ {ID: "id-1", Name: "machine-a", Addr: "fd00::1"}, }, }, { name: "not found single", store: &mockStore{machines: machines}, input: []string{"missing"}, wantErr: true, errMsg: "machine not found: missing", }, { name: "not found multiple", store: &mockStore{machines: machines}, input: []string{"missing", "also-missing"}, wantErr: true, errMsg: "machines not found: missing, also-missing", }, { name: "partial not found", store: &mockStore{machines: machines}, input: []string{"machine-a", "missing"}, wantErr: true, errMsg: "machine not found: missing", }, { name: "wildcard with no machines", store: &mockStore{machines: []*pb.MachineInfo{}}, input: []string{"*"}, wantErr: true, errMsg: "no machines in cluster", }, { name: "store error", store: &mockStore{err: errors.New("store down")}, input: []string{"*"}, wantErr: true, errMsg: "list machines: store down", }, { name: "invalid management ip", store: &mockStore{machines: []*pb.MachineInfo{{Id: "bad", Name: "bad-ip", Network: &pb.NetworkConfig{ManagementIp: &pb.IP{}}}}}, input: []string{"*"}, wantErr: true, errMsg: "invalid management IP for machine 'bad-ip' in store", }, { name: "empty input returns error", store: &mockStore{machines: machines}, input: []string{}, wantErr: true, errMsg: "no machines specified", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mapper := NewCorrosionMapper(tt.store) got, err := mapper.MapMachines(ctx, tt.input) if tt.wantErr { require.Error(t, err) assert.Contains(t, err.Error(), tt.errMsg) return } require.NoError(t, err) assert.Equal(t, tt.want, got) }) } }