Utility tool for upgrading talos nodes.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 487 lines 14 kB view raw
1package factory 2 3import ( 4 "encoding/json" 5 "io" 6 "net/http" 7 "net/http/httptest" 8 "net/url" 9 "testing" 10 11 "github.com/evanjarrett/homelab/internal/config" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 "gopkg.in/yaml.v3" 15) 16 17// ============================================================================ 18// NewClient() Tests 19// ============================================================================ 20 21func TestNewClient_DefaultURL(t *testing.T) { 22 client := NewClient("") 23 assert.Equal(t, "https://factory.talos.dev", client.baseURL) 24 assert.NotNil(t, client.httpClient) 25} 26 27func TestNewClient_CustomURL(t *testing.T) { 28 client := NewClient("https://custom.factory.dev") 29 assert.Equal(t, "https://custom.factory.dev", client.baseURL) 30} 31 32// ============================================================================ 33// BuildSchematic() Tests 34// ============================================================================ 35 36func TestBuildSchematic_MinimalProfile(t *testing.T) { 37 profile := config.Profile{ 38 Arch: "amd64", 39 Platform: "metal", 40 Extensions: []string{}, 41 } 42 43 schematic := BuildSchematic(profile) 44 require.NotNil(t, schematic) 45 assert.NotNil(t, schematic.Customization) 46 assert.Nil(t, schematic.Customization.SystemExtensions) 47 assert.Empty(t, schematic.Customization.ExtraKernelArgs) 48 assert.Nil(t, schematic.Overlay) 49} 50 51func TestBuildSchematic_WithExtensions(t *testing.T) { 52 profile := config.Profile{ 53 Arch: "amd64", 54 Platform: "metal", 55 Extensions: []string{ 56 "siderolabs/i915", 57 "siderolabs/iscsi-tools", 58 }, 59 } 60 61 schematic := BuildSchematic(profile) 62 require.NotNil(t, schematic) 63 require.NotNil(t, schematic.Customization) 64 require.NotNil(t, schematic.Customization.SystemExtensions) 65 assert.Len(t, schematic.Customization.SystemExtensions.OfficialExtensions, 2) 66 assert.Contains(t, schematic.Customization.SystemExtensions.OfficialExtensions, "siderolabs/i915") 67 assert.Contains(t, schematic.Customization.SystemExtensions.OfficialExtensions, "siderolabs/iscsi-tools") 68} 69 70func TestBuildSchematic_WithKernelArgs(t *testing.T) { 71 profile := config.Profile{ 72 Arch: "amd64", 73 Platform: "metal", 74 KernelArgs: []string{ 75 "amd_iommu=off", 76 "nomodeset", 77 }, 78 } 79 80 schematic := BuildSchematic(profile) 81 require.NotNil(t, schematic) 82 assert.Len(t, schematic.Customization.ExtraKernelArgs, 2) 83 assert.Contains(t, schematic.Customization.ExtraKernelArgs, "amd_iommu=off") 84 assert.Contains(t, schematic.Customization.ExtraKernelArgs, "nomodeset") 85} 86 87func TestBuildSchematic_WithOverlay(t *testing.T) { 88 profile := config.Profile{ 89 Arch: "arm64", 90 Platform: "metal", 91 Overlay: &config.Overlay{ 92 Name: "rpi_generic", 93 Image: "siderolabs/sbc-raspberrypi", 94 }, 95 } 96 97 schematic := BuildSchematic(profile) 98 require.NotNil(t, schematic) 99 require.NotNil(t, schematic.Overlay) 100 assert.Equal(t, "rpi_generic", schematic.Overlay.Name) 101 assert.Equal(t, "siderolabs/sbc-raspberrypi", schematic.Overlay.Image) 102} 103 104func TestBuildSchematic_CompleteProfile(t *testing.T) { 105 profile := config.Profile{ 106 Arch: "arm64", 107 Platform: "metal", 108 Secureboot: false, 109 KernelArgs: []string{"console=ttyS0"}, 110 Extensions: []string{"siderolabs/iscsi-tools"}, 111 Overlay: &config.Overlay{ 112 Name: "turingrk1", 113 Image: "siderolabs/sbc-rockchip", 114 }, 115 } 116 117 schematic := BuildSchematic(profile) 118 require.NotNil(t, schematic) 119 require.NotNil(t, schematic.Customization) 120 require.NotNil(t, schematic.Customization.SystemExtensions) 121 require.NotNil(t, schematic.Overlay) 122 123 assert.Len(t, schematic.Customization.ExtraKernelArgs, 1) 124 assert.Len(t, schematic.Customization.SystemExtensions.OfficialExtensions, 1) 125 assert.Equal(t, "turingrk1", schematic.Overlay.Name) 126} 127 128func TestBuildSchematic_YAMLOutput(t *testing.T) { 129 profile := config.Profile{ 130 Arch: "amd64", 131 Platform: "metal", 132 Extensions: []string{ 133 "siderolabs/i915", 134 }, 135 } 136 137 schematic := BuildSchematic(profile) 138 yamlBytes, err := yaml.Marshal(schematic) 139 require.NoError(t, err) 140 141 // Verify YAML can be unmarshalled back 142 var parsed Schematic 143 err = yaml.Unmarshal(yamlBytes, &parsed) 144 require.NoError(t, err) 145 require.NotNil(t, parsed.Customization.SystemExtensions) 146 assert.Contains(t, parsed.Customization.SystemExtensions.OfficialExtensions, "siderolabs/i915") 147} 148 149// ============================================================================ 150// GetSchematicID() Tests 151// ============================================================================ 152 153func TestGetSchematicID_Success(t *testing.T) { 154 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 155 // Verify request 156 assert.Equal(t, "POST", r.Method) 157 assert.Equal(t, "/schematics", r.URL.Path) 158 assert.Equal(t, "application/yaml", r.Header.Get("Content-Type")) 159 160 // Read and verify body is valid YAML 161 body, err := io.ReadAll(r.Body) 162 require.NoError(t, err) 163 var schematic Schematic 164 err = yaml.Unmarshal(body, &schematic) 165 assert.NoError(t, err) 166 167 // Return success 168 w.WriteHeader(http.StatusOK) 169 json.NewEncoder(w).Encode(SchematicResponse{ID: "abc123def456"}) 170 })) 171 defer server.Close() 172 173 client := NewClient(server.URL) 174 schematic := &Schematic{ 175 Customization: &SchematicCustomization{ 176 SystemExtensions: &SchematicSystemExtensions{ 177 OfficialExtensions: []string{"siderolabs/i915"}, 178 }, 179 }, 180 } 181 182 id, err := client.GetSchematicID(schematic) 183 require.NoError(t, err) 184 assert.Equal(t, "abc123def456", id) 185} 186 187func TestGetSchematicID_Created201(t *testing.T) { 188 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 189 w.WriteHeader(http.StatusCreated) 190 json.NewEncoder(w).Encode(SchematicResponse{ID: "newschematic123"}) 191 })) 192 defer server.Close() 193 194 client := NewClient(server.URL) 195 id, err := client.GetSchematicID(&Schematic{}) 196 require.NoError(t, err) 197 assert.Equal(t, "newschematic123", id) 198} 199 200func TestGetSchematicID_ServerError(t *testing.T) { 201 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 202 w.WriteHeader(http.StatusInternalServerError) 203 })) 204 defer server.Close() 205 206 client := NewClient(server.URL) 207 id, err := client.GetSchematicID(&Schematic{}) 208 assert.Error(t, err) 209 assert.Empty(t, id) 210 assert.Contains(t, err.Error(), "returned status 500") 211} 212 213func TestGetSchematicID_BadRequest(t *testing.T) { 214 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 215 w.WriteHeader(http.StatusBadRequest) 216 })) 217 defer server.Close() 218 219 client := NewClient(server.URL) 220 id, err := client.GetSchematicID(&Schematic{}) 221 assert.Error(t, err) 222 assert.Empty(t, id) 223 assert.Contains(t, err.Error(), "returned status 400") 224} 225 226func TestGetSchematicID_InvalidJSON(t *testing.T) { 227 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 228 w.WriteHeader(http.StatusOK) 229 w.Write([]byte("not json")) 230 })) 231 defer server.Close() 232 233 client := NewClient(server.URL) 234 id, err := client.GetSchematicID(&Schematic{}) 235 assert.Error(t, err) 236 assert.Empty(t, id) 237 assert.Contains(t, err.Error(), "failed to decode") 238} 239 240func TestGetSchematicID_EmptyID(t *testing.T) { 241 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 242 w.WriteHeader(http.StatusOK) 243 json.NewEncoder(w).Encode(SchematicResponse{ID: ""}) 244 })) 245 defer server.Close() 246 247 client := NewClient(server.URL) 248 id, err := client.GetSchematicID(&Schematic{}) 249 assert.Error(t, err) 250 assert.Empty(t, id) 251 assert.Contains(t, err.Error(), "empty schematic ID") 252} 253 254func TestGetSchematicID_ConnectionError(t *testing.T) { 255 client := NewClient("http://localhost:59999") 256 id, err := client.GetSchematicID(&Schematic{}) 257 assert.Error(t, err) 258 assert.Empty(t, id) 259 assert.Contains(t, err.Error(), "failed to post schematic") 260} 261 262// ============================================================================ 263// GetInstallerImage() Tests 264// ============================================================================ 265 266func TestGetInstallerImage_Standard(t *testing.T) { 267 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 268 w.WriteHeader(http.StatusOK) 269 json.NewEncoder(w).Encode(SchematicResponse{ID: "schematic123"}) 270 })) 271 defer server.Close() 272 273 client := NewClient(server.URL) 274 profile := config.Profile{ 275 Arch: "amd64", 276 Platform: "metal", 277 Secureboot: false, 278 Extensions: []string{"siderolabs/i915"}, 279 } 280 281 image, err := client.GetInstallerImage(profile, "1.7.0") 282 require.NoError(t, err) 283 assert.Equal(t, "factory.talos.dev/installer/schematic123:v1.7.0", image) 284} 285 286func TestGetInstallerImage_Secureboot(t *testing.T) { 287 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 288 w.WriteHeader(http.StatusOK) 289 json.NewEncoder(w).Encode(SchematicResponse{ID: "secureboot456"}) 290 })) 291 defer server.Close() 292 293 client := NewClient(server.URL) 294 profile := config.Profile{ 295 Arch: "amd64", 296 Platform: "metal", 297 Secureboot: true, 298 Extensions: []string{"siderolabs/i915"}, 299 } 300 301 image, err := client.GetInstallerImage(profile, "1.7.0") 302 require.NoError(t, err) 303 assert.Equal(t, "factory.talos.dev/installer-secureboot/secureboot456:v1.7.0", image) 304} 305 306func TestGetInstallerImage_APIError(t *testing.T) { 307 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 308 w.WriteHeader(http.StatusInternalServerError) 309 })) 310 defer server.Close() 311 312 client := NewClient(server.URL) 313 profile := config.Profile{ 314 Arch: "amd64", 315 Platform: "metal", 316 } 317 318 image, err := client.GetInstallerImage(profile, "1.7.0") 319 assert.Error(t, err) 320 assert.Empty(t, image) 321} 322 323// ============================================================================ 324// GenerateFactoryURL() Tests 325// ============================================================================ 326 327func TestGenerateFactoryURL_BasicProfile(t *testing.T) { 328 profile := config.Profile{ 329 Arch: "amd64", 330 Platform: "metal", 331 Secureboot: false, 332 } 333 334 urlStr := GenerateFactoryURL(profile, "1.7.0", "") 335 336 parsed, err := url.Parse(urlStr) 337 require.NoError(t, err) 338 339 assert.Equal(t, "https", parsed.Scheme) 340 assert.Equal(t, "factory.talos.dev", parsed.Host) 341 assert.Equal(t, "/", parsed.Path) 342 343 params := parsed.Query() 344 assert.Equal(t, "amd64", params.Get("arch")) 345 assert.Equal(t, "metal", params.Get("platform")) 346 assert.Equal(t, "metal", params.Get("target")) 347 assert.Equal(t, "1.7.0", params.Get("version")) 348 assert.Equal(t, "auto", params.Get("bootloader")) 349 assert.Equal(t, "true", params.Get("cmdline-set")) 350 assert.Empty(t, params.Get("secureboot")) 351} 352 353func TestGenerateFactoryURL_Secureboot(t *testing.T) { 354 profile := config.Profile{ 355 Arch: "amd64", 356 Platform: "metal", 357 Secureboot: true, 358 } 359 360 urlStr := GenerateFactoryURL(profile, "1.7.0", "") 361 362 parsed, err := url.Parse(urlStr) 363 require.NoError(t, err) 364 365 params := parsed.Query() 366 assert.Equal(t, "true", params.Get("secureboot")) 367 assert.Equal(t, "metal", params.Get("target")) 368} 369 370func TestGenerateFactoryURL_WithExtensions(t *testing.T) { 371 profile := config.Profile{ 372 Arch: "amd64", 373 Platform: "metal", 374 Extensions: []string{ 375 "siderolabs/i915", 376 "siderolabs/iscsi-tools", 377 }, 378 } 379 380 urlStr := GenerateFactoryURL(profile, "1.7.0", "") 381 382 parsed, err := url.Parse(urlStr) 383 require.NoError(t, err) 384 385 params := parsed.Query() 386 extensions := params["extensions"] 387 assert.Len(t, extensions, 2) 388 assert.Contains(t, extensions, "siderolabs/i915") 389 assert.Contains(t, extensions, "siderolabs/iscsi-tools") 390} 391 392func TestGenerateFactoryURL_WithKernelArgs(t *testing.T) { 393 profile := config.Profile{ 394 Arch: "amd64", 395 Platform: "metal", 396 KernelArgs: []string{ 397 "amd_iommu=off", 398 "nomodeset", 399 }, 400 } 401 402 urlStr := GenerateFactoryURL(profile, "1.7.0", "") 403 404 parsed, err := url.Parse(urlStr) 405 require.NoError(t, err) 406 407 params := parsed.Query() 408 cmdline := params["cmdline"] 409 assert.Len(t, cmdline, 2) 410 assert.Contains(t, cmdline, "amd_iommu=off") 411 assert.Contains(t, cmdline, "nomodeset") 412} 413 414func TestGenerateFactoryURL_SBCWithOverlay(t *testing.T) { 415 profile := config.Profile{ 416 Arch: "arm64", 417 Platform: "metal", 418 Overlay: &config.Overlay{ 419 Name: "rpi_generic", 420 Image: "siderolabs/sbc-raspberrypi", 421 }, 422 Extensions: []string{"siderolabs/iscsi-tools"}, 423 } 424 425 urlStr := GenerateFactoryURL(profile, "1.7.0", "") 426 427 parsed, err := url.Parse(urlStr) 428 require.NoError(t, err) 429 430 params := parsed.Query() 431 assert.Equal(t, "arm64", params.Get("arch")) 432 assert.Equal(t, "sbc", params.Get("target")) 433 assert.Equal(t, "rpi_generic", params.Get("board")) 434 assert.Empty(t, params.Get("secureboot")) // SBCs don't use secureboot param 435 436 // SBCs should have "-" to reset defaults, then extensions 437 extensions := params["extensions"] 438 assert.Contains(t, extensions, "-") 439 assert.Contains(t, extensions, "siderolabs/iscsi-tools") 440} 441 442func TestGenerateFactoryURL_CustomBaseURL(t *testing.T) { 443 profile := config.Profile{ 444 Arch: "amd64", 445 Platform: "metal", 446 } 447 448 urlStr := GenerateFactoryURL(profile, "1.7.0", "https://custom.factory.dev") 449 450 parsed, err := url.Parse(urlStr) 451 require.NoError(t, err) 452 453 assert.Equal(t, "https", parsed.Scheme) 454 assert.Equal(t, "custom.factory.dev", parsed.Host) 455} 456 457func TestGenerateFactoryURL_CompleteProfile(t *testing.T) { 458 profile := config.Profile{ 459 Arch: "amd64", 460 Platform: "metal", 461 Secureboot: true, 462 KernelArgs: []string{"console=ttyS0"}, 463 Extensions: []string{ 464 "siderolabs/i915", 465 "siderolabs/nut-client", 466 }, 467 } 468 469 urlStr := GenerateFactoryURL(profile, "1.8.0", "") 470 471 parsed, err := url.Parse(urlStr) 472 require.NoError(t, err) 473 474 params := parsed.Query() 475 assert.Equal(t, "amd64", params.Get("arch")) 476 assert.Equal(t, "metal", params.Get("platform")) 477 assert.Equal(t, "metal", params.Get("target")) 478 assert.Equal(t, "true", params.Get("secureboot")) 479 assert.Equal(t, "1.8.0", params.Get("version")) 480 481 cmdline := params["cmdline"] 482 assert.Contains(t, cmdline, "console=ttyS0") 483 484 extensions := params["extensions"] 485 assert.Contains(t, extensions, "siderolabs/i915") 486 assert.Contains(t, extensions, "siderolabs/nut-client") 487}