fix(cli): preserve routes loader errors
Surface route loader and remotes config failures in list and status output so broken targets render explicit errors instead of appearing empty.
- commit
- 65770474de4ef4c2bfa6a137fc91821b7018480e
- author
- brice · · 2026-04-15 04:39 UTC
- parent
- 4648a7b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +206 -56
M internal/adapters/in/cli/routes.go +50 -20
| @@ -128,14 +128,14 @@ type routeStatusSection struct { | |||
| 128 | 128 | } | |
| 129 | 129 | ||
| 130 | 130 | type routesListDeps struct { | |
| 131 | - | explicitRemote func() (*remote.ResolvedRemote, bool) | |
| 131 | + | explicitRemote func() (*remote.ResolvedRemote, bool, error) | |
| 132 | 132 | loadLocal func(context.Context, string) (routeListSection, error) | |
| 133 | 133 | listRemotes func() (map[string]remote.RemoteEntry, string, error) | |
| 134 | 134 | loadRemote func(context.Context, string, remote.RemoteEntry) (routeListSection, error) | |
| 135 | 135 | } | |
| 136 | 136 | ||
| 137 | 137 | type routesStatusDeps struct { | |
| 138 | - | explicitRemote func() (*remote.ResolvedRemote, bool) | |
| 138 | + | explicitRemote func() (*remote.ResolvedRemote, bool, error) | |
| 139 | 139 | loadLocal func(context.Context, string) (routeStatusSection, error) | |
| 140 | 140 | listRemotes func() (map[string]remote.RemoteEntry, string, error) | |
| 141 | 141 | loadRemote func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) | |
| @@ -265,7 +265,9 @@ func collectRoutesListSections(ctx context.Context, cfgPath string, deps routesL | |||
| 265 | 265 | deps.loadRemote = loadRoutesListRemoteSection | |
| 266 | 266 | } | |
| 267 | 267 | ||
| 268 | - | if resolved, ok := deps.explicitRemote(); ok { | |
| 268 | + | if resolved, ok, err := deps.explicitRemote(); err != nil { | |
| 269 | + | return nil, err | |
| 270 | + | } else if ok { | |
| 269 | 271 | section := loadRoutesListExplicitRemoteSection(ctx, deps, resolved) | |
| 270 | 272 | return []routeListSection{section}, nil | |
| 271 | 273 | } | |
| @@ -281,7 +283,10 @@ func collectRoutesListSections(ctx context.Context, cfgPath string, deps routesL | |||
| 281 | 283 | } | |
| 282 | 284 | ||
| 283 | 285 | func loadRoutesListExplicitRemoteSection(ctx context.Context, deps routesListDeps, resolved *remote.ResolvedRemote) routeListSection { | |
| 284 | - | section, _ := deps.loadRemote(ctx, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | |
| 286 | + | section, err := deps.loadRemote(ctx, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | |
| 287 | + | if err != nil && section.Error == "" { | |
| 288 | + | section.Error = err.Error() | |
| 289 | + | } | |
| 285 | 290 | return normalizeRouteListRemoteSection(section, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | |
| 286 | 291 | } | |
| 287 | 292 | ||
| @@ -289,6 +294,7 @@ func collectRoutesListAggregateSections(ctx context.Context, cfgPath string, dep | |||
| 289 | 294 | ||
| 290 | 295 | type localResult struct { | |
| 291 | 296 | section routeListSection | |
| 297 | + | err error | |
| 292 | 298 | } | |
| 293 | 299 | type remotesResult struct { | |
| 294 | 300 | entries map[string]remote.RemoteEntry | |
| @@ -299,16 +305,22 @@ func collectRoutesListAggregateSections(ctx context.Context, cfgPath string, dep | |||
| 299 | 305 | remotesCh := make(chan remotesResult, 1) | |
| 300 | 306 | ||
| 301 | 307 | go func() { | |
| 302 | - | section, _ := deps.loadLocal(ctx, cfgPath) | |
| 303 | - | localCh <- localResult{section: section} | |
| 308 | + | section, err := deps.loadLocal(ctx, cfgPath) | |
| 309 | + | localCh <- localResult{section: section, err: err} | |
| 304 | 310 | }() | |
| 305 | 311 | go func() { | |
| 306 | 312 | entries, _, err := deps.listRemotes() | |
| 307 | 313 | remotesCh <- remotesResult{entries: entries, err: err} | |
| 308 | 314 | }() | |
| 309 | 315 | ||
| 310 | - | local := (<-localCh).section | |
| 316 | + | localRes := <-localCh | |
| 311 | 317 | remotes := <-remotesCh | |
| 318 | + | local := localRes.section | |
| 319 | + | if err := localRes.err; err != nil { | |
| 320 | + | if local.Error == "" { | |
| 321 | + | local.Error = err.Error() | |
| 322 | + | } | |
| 323 | + | } | |
| 312 | 324 | ||
| 313 | 325 | sections := make([]routeListSection, 0, 1) | |
| 314 | 326 | sections = append(sections, normalizeRouteListLocalSection(local)) | |
| @@ -327,7 +339,10 @@ func collectRoutesListAggregateSections(ctx context.Context, cfgPath string, dep | |||
| 327 | 339 | wg.Add(1) | |
| 328 | 340 | go func() { | |
| 329 | 341 | defer wg.Done() | |
| 330 | - | section, _ := deps.loadRemote(ctx, name, entry) | |
| 342 | + | section, err := deps.loadRemote(ctx, name, entry) | |
| 343 | + | if err != nil && section.Error == "" { | |
| 344 | + | section.Error = err.Error() | |
| 345 | + | } | |
| 331 | 346 | remoteSections[i] = normalizeRouteListRemoteSection(section, name, entry) | |
| 332 | 347 | }() | |
| 333 | 348 | } | |
| @@ -374,7 +389,9 @@ func collectRoutesStatusSections(ctx context.Context, cfgPath string, deps route | |||
| 374 | 389 | deps.loadRemote = loadRoutesStatusRemoteSection | |
| 375 | 390 | } | |
| 376 | 391 | ||
| 377 | - | if resolved, ok := deps.explicitRemote(); ok { | |
| 392 | + | if resolved, ok, err := deps.explicitRemote(); err != nil { | |
| 393 | + | return nil, err | |
| 394 | + | } else if ok { | |
| 378 | 395 | section := loadRoutesStatusExplicitRemoteSection(ctx, deps, resolved) | |
| 379 | 396 | return []routeStatusSection{section}, nil | |
| 380 | 397 | } | |
| @@ -390,7 +407,10 @@ func collectRoutesStatusSections(ctx context.Context, cfgPath string, deps route | |||
| 390 | 407 | } | |
| 391 | 408 | ||
| 392 | 409 | func loadRoutesStatusExplicitRemoteSection(ctx context.Context, deps routesStatusDeps, resolved *remote.ResolvedRemote) routeStatusSection { | |
| 393 | - | section, _ := deps.loadRemote(ctx, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | |
| 410 | + | section, err := deps.loadRemote(ctx, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | |
| 411 | + | if err != nil && section.Error == "" { | |
| 412 | + | section.Error = err.Error() | |
| 413 | + | } | |
| 394 | 414 | return normalizeRouteStatusRemoteSection(section, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | |
| 395 | 415 | } | |
| 396 | 416 | ||
| @@ -398,6 +418,7 @@ func collectRoutesStatusAggregateSections(ctx context.Context, cfgPath string, d | |||
| 398 | 418 | ||
| 399 | 419 | type localResult struct { | |
| 400 | 420 | section routeStatusSection | |
| 421 | + | err error | |
| 401 | 422 | } | |
| 402 | 423 | type remotesResult struct { | |
| 403 | 424 | entries map[string]remote.RemoteEntry | |
| @@ -408,16 +429,22 @@ func collectRoutesStatusAggregateSections(ctx context.Context, cfgPath string, d | |||
| 408 | 429 | remotesCh := make(chan remotesResult, 1) | |
| 409 | 430 | ||
| 410 | 431 | go func() { | |
| 411 | - | section, _ := deps.loadLocal(ctx, cfgPath) | |
| 412 | - | localCh <- localResult{section: section} | |
| 432 | + | section, err := deps.loadLocal(ctx, cfgPath) | |
| 433 | + | localCh <- localResult{section: section, err: err} | |
| 413 | 434 | }() | |
| 414 | 435 | go func() { | |
| 415 | 436 | entries, _, err := deps.listRemotes() | |
| 416 | 437 | remotesCh <- remotesResult{entries: entries, err: err} | |
| 417 | 438 | }() | |
| 418 | 439 | ||
| 419 | - | local := (<-localCh).section | |
| 440 | + | localRes := <-localCh | |
| 420 | 441 | remotes := <-remotesCh | |
| 442 | + | local := localRes.section | |
| 443 | + | if err := localRes.err; err != nil { | |
| 444 | + | if local.Error == "" { | |
| 445 | + | local.Error = err.Error() | |
| 446 | + | } | |
| 447 | + | } | |
| 421 | 448 | ||
| 422 | 449 | sections := make([]routeStatusSection, 0, 1) | |
| 423 | 450 | sections = append(sections, normalizeRouteStatusLocalSection(local)) | |
| @@ -436,7 +463,10 @@ func collectRoutesStatusAggregateSections(ctx context.Context, cfgPath string, d | |||
| 436 | 463 | wg.Add(1) | |
| 437 | 464 | go func() { | |
| 438 | 465 | defer wg.Done() | |
| 439 | - | section, _ := deps.loadRemote(ctx, name, entry) | |
| 466 | + | section, err := deps.loadRemote(ctx, name, entry) | |
| 467 | + | if err != nil && section.Error == "" { | |
| 468 | + | section.Error = err.Error() | |
| 469 | + | } | |
| 440 | 470 | remoteSections[i] = normalizeRouteStatusRemoteSection(section, name, entry) | |
| 441 | 471 | }() | |
| 442 | 472 | } | |
| @@ -603,10 +633,10 @@ func unresolvedRoutesTargetError(target string) string { | |||
| 603 | 633 | return fmt.Sprintf("could not resolve remote target %q", target) | |
| 604 | 634 | } | |
| 605 | 635 | ||
| 606 | - | func resolveRoutesExplicitRemote() (*remote.ResolvedRemote, bool) { | |
| 636 | + | func resolveRoutesExplicitRemote() (*remote.ResolvedRemote, bool, error) { | |
| 607 | 637 | target, ok := resolveRoutesExplicitTarget() | |
| 608 | 638 | if !ok { | |
| 609 | - | return nil, false | |
| 639 | + | return nil, false, nil | |
| 610 | 640 | } | |
| 611 | 641 | ||
| 612 | 642 | if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") { | |
| @@ -614,12 +644,12 @@ func resolveRoutesExplicitRemote() (*remote.ResolvedRemote, bool) { | |||
| 614 | 644 | URL: target, | |
| 615 | 645 | Token: resolveRoutesTokenForTarget("", remote.RemoteEntry{}), | |
| 616 | 646 | InsecureTLS: resolveRoutesInsecureForTarget("", remote.RemoteEntry{}), | |
| 617 | - | }, true | |
| 647 | + | }, true, nil | |
| 618 | 648 | } | |
| 619 | 649 | ||
| 620 | 650 | remotes, err := remote.LoadRemotes("") | |
| 621 | 651 | if err != nil { | |
| 622 | - | return nil, false | |
| 652 | + | return nil, false, err | |
| 623 | 653 | } | |
| 624 | 654 | ||
| 625 | 655 | if remotes != nil { | |
| @@ -629,11 +659,11 @@ func resolveRoutesExplicitRemote() (*remote.ResolvedRemote, bool) { | |||
| 629 | 659 | URL: entry.URL, | |
| 630 | 660 | Token: resolveRoutesTokenForTarget(target, entry), | |
| 631 | 661 | InsecureTLS: resolveRoutesInsecureForTarget(target, entry), | |
| 632 | - | }, true | |
| 662 | + | }, true, nil | |
| 633 | 663 | } | |
| 634 | 664 | } | |
| 635 | 665 | ||
| 636 | - | return nil, false | |
| 666 | + | return nil, false, nil | |
| 637 | 667 | } | |
| 638 | 668 | ||
| 639 | 669 | func resolveRoutesExplicitTarget() (string, bool) { | |
| @@ -128,14 +128,14 @@ type routeStatusSection struct { | |||||
| 128 | } | 128 | } | ||
| 129 | 129 | ||||
| 130 | type routesListDeps struct { | 130 | type routesListDeps struct { | ||
| 131 | - | explicitRemote func() (*remote.ResolvedRemote, bool) | 131 | + | explicitRemote func() (*remote.ResolvedRemote, bool, error) |
| 132 | loadLocal func(context.Context, string) (routeListSection, error) | 132 | loadLocal func(context.Context, string) (routeListSection, error) | ||
| 133 | listRemotes func() (map[string]remote.RemoteEntry, string, error) | 133 | listRemotes func() (map[string]remote.RemoteEntry, string, error) | ||
| 134 | loadRemote func(context.Context, string, remote.RemoteEntry) (routeListSection, error) | 134 | loadRemote func(context.Context, string, remote.RemoteEntry) (routeListSection, error) | ||
| 135 | } | 135 | } | ||
| 136 | 136 | ||||
| 137 | type routesStatusDeps struct { | 137 | type routesStatusDeps struct { | ||
| 138 | - | explicitRemote func() (*remote.ResolvedRemote, bool) | 138 | + | explicitRemote func() (*remote.ResolvedRemote, bool, error) |
| 139 | loadLocal func(context.Context, string) (routeStatusSection, error) | 139 | loadLocal func(context.Context, string) (routeStatusSection, error) | ||
| 140 | listRemotes func() (map[string]remote.RemoteEntry, string, error) | 140 | listRemotes func() (map[string]remote.RemoteEntry, string, error) | ||
| 141 | loadRemote func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) | 141 | loadRemote func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) | ||
| @@ -265,7 +265,9 @@ func collectRoutesListSections(ctx context.Context, cfgPath string, deps routesL | |||||
| 265 | deps.loadRemote = loadRoutesListRemoteSection | 265 | deps.loadRemote = loadRoutesListRemoteSection | ||
| 266 | } | 266 | } | ||
| 267 | 267 | ||||
| 268 | - | if resolved, ok := deps.explicitRemote(); ok { | 268 | + | if resolved, ok, err := deps.explicitRemote(); err != nil { |
| 269 | + | return nil, err | |||
| 270 | + | } else if ok { | |||
| 269 | section := loadRoutesListExplicitRemoteSection(ctx, deps, resolved) | 271 | section := loadRoutesListExplicitRemoteSection(ctx, deps, resolved) | ||
| 270 | return []routeListSection{section}, nil | 272 | return []routeListSection{section}, nil | ||
| 271 | } | 273 | } | ||
| @@ -281,7 +283,10 @@ func collectRoutesListSections(ctx context.Context, cfgPath string, deps routesL | |||||
| 281 | } | 283 | } | ||
| 282 | 284 | ||||
| 283 | func loadRoutesListExplicitRemoteSection(ctx context.Context, deps routesListDeps, resolved *remote.ResolvedRemote) routeListSection { | 285 | func loadRoutesListExplicitRemoteSection(ctx context.Context, deps routesListDeps, resolved *remote.ResolvedRemote) routeListSection { | ||
| 284 | - | section, _ := deps.loadRemote(ctx, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | 286 | + | section, err := deps.loadRemote(ctx, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) |
| 287 | + | if err != nil && section.Error == "" { | |||
| 288 | + | section.Error = err.Error() | |||
| 289 | + | } | |||
| 285 | return normalizeRouteListRemoteSection(section, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | 290 | return normalizeRouteListRemoteSection(section, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | ||
| 286 | } | 291 | } | ||
| 287 | 292 | ||||
| @@ -289,6 +294,7 @@ func collectRoutesListAggregateSections(ctx context.Context, cfgPath string, dep | |||||
| 289 | 294 | ||||
| 290 | type localResult struct { | 295 | type localResult struct { | ||
| 291 | section routeListSection | 296 | section routeListSection | ||
| 297 | + | err error | |||
| 292 | } | 298 | } | ||
| 293 | type remotesResult struct { | 299 | type remotesResult struct { | ||
| 294 | entries map[string]remote.RemoteEntry | 300 | entries map[string]remote.RemoteEntry | ||
| @@ -299,16 +305,22 @@ func collectRoutesListAggregateSections(ctx context.Context, cfgPath string, dep | |||||
| 299 | remotesCh := make(chan remotesResult, 1) | 305 | remotesCh := make(chan remotesResult, 1) | ||
| 300 | 306 | ||||
| 301 | go func() { | 307 | go func() { | ||
| 302 | - | section, _ := deps.loadLocal(ctx, cfgPath) | 308 | + | section, err := deps.loadLocal(ctx, cfgPath) |
| 303 | - | localCh <- localResult{section: section} | 309 | + | localCh <- localResult{section: section, err: err} |
| 304 | }() | 310 | }() | ||
| 305 | go func() { | 311 | go func() { | ||
| 306 | entries, _, err := deps.listRemotes() | 312 | entries, _, err := deps.listRemotes() | ||
| 307 | remotesCh <- remotesResult{entries: entries, err: err} | 313 | remotesCh <- remotesResult{entries: entries, err: err} | ||
| 308 | }() | 314 | }() | ||
| 309 | 315 | ||||
| 310 | - | local := (<-localCh).section | 316 | + | localRes := <-localCh |
| 311 | remotes := <-remotesCh | 317 | remotes := <-remotesCh | ||
| 318 | + | local := localRes.section | |||
| 319 | + | if err := localRes.err; err != nil { | |||
| 320 | + | if local.Error == "" { | |||
| 321 | + | local.Error = err.Error() | |||
| 322 | + | } | |||
| 323 | + | } | |||
| 312 | 324 | ||||
| 313 | sections := make([]routeListSection, 0, 1) | 325 | sections := make([]routeListSection, 0, 1) | ||
| 314 | sections = append(sections, normalizeRouteListLocalSection(local)) | 326 | sections = append(sections, normalizeRouteListLocalSection(local)) | ||
| @@ -327,7 +339,10 @@ func collectRoutesListAggregateSections(ctx context.Context, cfgPath string, dep | |||||
| 327 | wg.Add(1) | 339 | wg.Add(1) | ||
| 328 | go func() { | 340 | go func() { | ||
| 329 | defer wg.Done() | 341 | defer wg.Done() | ||
| 330 | - | section, _ := deps.loadRemote(ctx, name, entry) | 342 | + | section, err := deps.loadRemote(ctx, name, entry) |
| 343 | + | if err != nil && section.Error == "" { | |||
| 344 | + | section.Error = err.Error() | |||
| 345 | + | } | |||
| 331 | remoteSections[i] = normalizeRouteListRemoteSection(section, name, entry) | 346 | remoteSections[i] = normalizeRouteListRemoteSection(section, name, entry) | ||
| 332 | }() | 347 | }() | ||
| 333 | } | 348 | } | ||
| @@ -374,7 +389,9 @@ func collectRoutesStatusSections(ctx context.Context, cfgPath string, deps route | |||||
| 374 | deps.loadRemote = loadRoutesStatusRemoteSection | 389 | deps.loadRemote = loadRoutesStatusRemoteSection | ||
| 375 | } | 390 | } | ||
| 376 | 391 | ||||
| 377 | - | if resolved, ok := deps.explicitRemote(); ok { | 392 | + | if resolved, ok, err := deps.explicitRemote(); err != nil { |
| 393 | + | return nil, err | |||
| 394 | + | } else if ok { | |||
| 378 | section := loadRoutesStatusExplicitRemoteSection(ctx, deps, resolved) | 395 | section := loadRoutesStatusExplicitRemoteSection(ctx, deps, resolved) | ||
| 379 | return []routeStatusSection{section}, nil | 396 | return []routeStatusSection{section}, nil | ||
| 380 | } | 397 | } | ||
| @@ -390,7 +407,10 @@ func collectRoutesStatusSections(ctx context.Context, cfgPath string, deps route | |||||
| 390 | } | 407 | } | ||
| 391 | 408 | ||||
| 392 | func loadRoutesStatusExplicitRemoteSection(ctx context.Context, deps routesStatusDeps, resolved *remote.ResolvedRemote) routeStatusSection { | 409 | func loadRoutesStatusExplicitRemoteSection(ctx context.Context, deps routesStatusDeps, resolved *remote.ResolvedRemote) routeStatusSection { | ||
| 393 | - | section, _ := deps.loadRemote(ctx, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | 410 | + | section, err := deps.loadRemote(ctx, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) |
| 411 | + | if err != nil && section.Error == "" { | |||
| 412 | + | section.Error = err.Error() | |||
| 413 | + | } | |||
| 394 | return normalizeRouteStatusRemoteSection(section, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | 414 | return normalizeRouteStatusRemoteSection(section, resolved.DisplayName(), remote.RemoteEntry{URL: resolved.URL, Token: resolved.Token, InsecureTLS: resolved.InsecureTLS}) | ||
| 395 | } | 415 | } | ||
| 396 | 416 | ||||
| @@ -398,6 +418,7 @@ func collectRoutesStatusAggregateSections(ctx context.Context, cfgPath string, d | |||||
| 398 | 418 | ||||
| 399 | type localResult struct { | 419 | type localResult struct { | ||
| 400 | section routeStatusSection | 420 | section routeStatusSection | ||
| 421 | + | err error | |||
| 401 | } | 422 | } | ||
| 402 | type remotesResult struct { | 423 | type remotesResult struct { | ||
| 403 | entries map[string]remote.RemoteEntry | 424 | entries map[string]remote.RemoteEntry | ||
| @@ -408,16 +429,22 @@ func collectRoutesStatusAggregateSections(ctx context.Context, cfgPath string, d | |||||
| 408 | remotesCh := make(chan remotesResult, 1) | 429 | remotesCh := make(chan remotesResult, 1) | ||
| 409 | 430 | ||||
| 410 | go func() { | 431 | go func() { | ||
| 411 | - | section, _ := deps.loadLocal(ctx, cfgPath) | 432 | + | section, err := deps.loadLocal(ctx, cfgPath) |
| 412 | - | localCh <- localResult{section: section} | 433 | + | localCh <- localResult{section: section, err: err} |
| 413 | }() | 434 | }() | ||
| 414 | go func() { | 435 | go func() { | ||
| 415 | entries, _, err := deps.listRemotes() | 436 | entries, _, err := deps.listRemotes() | ||
| 416 | remotesCh <- remotesResult{entries: entries, err: err} | 437 | remotesCh <- remotesResult{entries: entries, err: err} | ||
| 417 | }() | 438 | }() | ||
| 418 | 439 | ||||
| 419 | - | local := (<-localCh).section | 440 | + | localRes := <-localCh |
| 420 | remotes := <-remotesCh | 441 | remotes := <-remotesCh | ||
| 442 | + | local := localRes.section | |||
| 443 | + | if err := localRes.err; err != nil { | |||
| 444 | + | if local.Error == "" { | |||
| 445 | + | local.Error = err.Error() | |||
| 446 | + | } | |||
| 447 | + | } | |||
| 421 | 448 | ||||
| 422 | sections := make([]routeStatusSection, 0, 1) | 449 | sections := make([]routeStatusSection, 0, 1) | ||
| 423 | sections = append(sections, normalizeRouteStatusLocalSection(local)) | 450 | sections = append(sections, normalizeRouteStatusLocalSection(local)) | ||
| @@ -436,7 +463,10 @@ func collectRoutesStatusAggregateSections(ctx context.Context, cfgPath string, d | |||||
| 436 | wg.Add(1) | 463 | wg.Add(1) | ||
| 437 | go func() { | 464 | go func() { | ||
| 438 | defer wg.Done() | 465 | defer wg.Done() | ||
| 439 | - | section, _ := deps.loadRemote(ctx, name, entry) | 466 | + | section, err := deps.loadRemote(ctx, name, entry) |
| 467 | + | if err != nil && section.Error == "" { | |||
| 468 | + | section.Error = err.Error() | |||
| 469 | + | } | |||
| 440 | remoteSections[i] = normalizeRouteStatusRemoteSection(section, name, entry) | 470 | remoteSections[i] = normalizeRouteStatusRemoteSection(section, name, entry) | ||
| 441 | }() | 471 | }() | ||
| 442 | } | 472 | } | ||
| @@ -603,10 +633,10 @@ func unresolvedRoutesTargetError(target string) string { | |||||
| 603 | return fmt.Sprintf("could not resolve remote target %q", target) | 633 | return fmt.Sprintf("could not resolve remote target %q", target) | ||
| 604 | } | 634 | } | ||
| 605 | 635 | ||||
| 606 | - | func resolveRoutesExplicitRemote() (*remote.ResolvedRemote, bool) { | 636 | + | func resolveRoutesExplicitRemote() (*remote.ResolvedRemote, bool, error) { |
| 607 | target, ok := resolveRoutesExplicitTarget() | 637 | target, ok := resolveRoutesExplicitTarget() | ||
| 608 | if !ok { | 638 | if !ok { | ||
| 609 | - | return nil, false | 639 | + | return nil, false, nil |
| 610 | } | 640 | } | ||
| 611 | 641 | ||||
| 612 | if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") { | 642 | if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") { | ||
| @@ -614,12 +644,12 @@ func resolveRoutesExplicitRemote() (*remote.ResolvedRemote, bool) { | |||||
| 614 | URL: target, | 644 | URL: target, | ||
| 615 | Token: resolveRoutesTokenForTarget("", remote.RemoteEntry{}), | 645 | Token: resolveRoutesTokenForTarget("", remote.RemoteEntry{}), | ||
| 616 | InsecureTLS: resolveRoutesInsecureForTarget("", remote.RemoteEntry{}), | 646 | InsecureTLS: resolveRoutesInsecureForTarget("", remote.RemoteEntry{}), | ||
| 617 | - | }, true | 647 | + | }, true, nil |
| 618 | } | 648 | } | ||
| 619 | 649 | ||||
| 620 | remotes, err := remote.LoadRemotes("") | 650 | remotes, err := remote.LoadRemotes("") | ||
| 621 | if err != nil { | 651 | if err != nil { | ||
| 622 | - | return nil, false | 652 | + | return nil, false, err |
| 623 | } | 653 | } | ||
| 624 | 654 | ||||
| 625 | if remotes != nil { | 655 | if remotes != nil { | ||
| @@ -629,11 +659,11 @@ func resolveRoutesExplicitRemote() (*remote.ResolvedRemote, bool) { | |||||
| 629 | URL: entry.URL, | 659 | URL: entry.URL, | ||
| 630 | Token: resolveRoutesTokenForTarget(target, entry), | 660 | Token: resolveRoutesTokenForTarget(target, entry), | ||
| 631 | InsecureTLS: resolveRoutesInsecureForTarget(target, entry), | 661 | InsecureTLS: resolveRoutesInsecureForTarget(target, entry), | ||
| 632 | - | }, true | 662 | + | }, true, nil |
| 633 | } | 663 | } | ||
| 634 | } | 664 | } | ||
| 635 | 665 | ||||
| 636 | - | return nil, false | 666 | + | return nil, false, nil |
| 637 | } | 667 | } | ||
| 638 | 668 | ||||
| 639 | func resolveRoutesExplicitTarget() (string, bool) { | 669 | func resolveRoutesExplicitTarget() (string, bool) { | ||
M internal/adapters/in/cli/routes_test.go +156 -36
| @@ -249,13 +249,43 @@ func TestResolveRoutesExplicitRemote_AllowsAdHocURLWhenSavedRemotesConfigUnreada | |||
| 249 | 249 | configPath := filepath.Join(configHome, "gordon", "remotes.toml") | |
| 250 | 250 | require.NoError(t, os.MkdirAll(configPath, 0o755)) | |
| 251 | 251 | ||
| 252 | - | resolved, ok := resolveRoutesExplicitRemote() | |
| 252 | + | resolved, ok, err := resolveRoutesExplicitRemote() | |
| 253 | 253 | require.True(t, ok) | |
| 254 | + | require.NoError(t, err) | |
| 254 | 255 | require.NotNil(t, resolved) | |
| 255 | 256 | assert.Equal(t, "https://ad-hoc.example.com", resolved.URL) | |
| 256 | 257 | assert.Empty(t, resolved.Name) | |
| 257 | 258 | } | |
| 258 | 259 | ||
| 260 | + | func TestResolveRoutesExplicitRemote_ReturnsErrorForUnreadableNamedRemoteConfig(t *testing.T) { | |
| 261 | + | originalRemoteFlag := remoteFlag | |
| 262 | + | originalTokenFlag := tokenFlag | |
| 263 | + | originalInsecureTLSFlag := insecureTLSFlag | |
| 264 | + | t.Cleanup(func() { | |
| 265 | + | remoteFlag = originalRemoteFlag | |
| 266 | + | tokenFlag = originalTokenFlag | |
| 267 | + | insecureTLSFlag = originalInsecureTLSFlag | |
| 268 | + | }) | |
| 269 | + | ||
| 270 | + | remoteFlag = "missing-remote" | |
| 271 | + | tokenFlag = "" | |
| 272 | + | insecureTLSFlag = false | |
| 273 | + | ||
| 274 | + | configHome := t.TempDir() | |
| 275 | + | t.Setenv("XDG_CONFIG_HOME", configHome) | |
| 276 | + | t.Setenv("HOME", t.TempDir()) | |
| 277 | + | t.Setenv("GORDON_REMOTE", "") | |
| 278 | + | ||
| 279 | + | configPath := filepath.Join(configHome, "gordon", "remotes.toml") | |
| 280 | + | require.NoError(t, os.MkdirAll(configPath, 0o755)) | |
| 281 | + | ||
| 282 | + | resolved, ok, err := resolveRoutesExplicitRemote() | |
| 283 | + | require.Error(t, err) | |
| 284 | + | require.False(t, ok) | |
| 285 | + | require.Nil(t, resolved) | |
| 286 | + | assert.Contains(t, err.Error(), "failed to read remotes") | |
| 287 | + | } | |
| 288 | + | ||
| 259 | 289 | func TestRouteStatusTitle_PreservesProbeFailureError(t *testing.T) { | |
| 260 | 290 | item := routeStatusItem{ | |
| 261 | 291 | Domain: "app.example.com", | |
| @@ -325,8 +355,8 @@ func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | |||
| 325 | 355 | ||
| 326 | 356 | func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) { | |
| 327 | 357 | testsDeps := routesListDeps{ | |
| 328 | - | explicitRemote: func() (*remote.ResolvedRemote, bool) { | |
| 329 | - | return nil, false | |
| 358 | + | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { | |
| 359 | + | return nil, false, nil | |
| 330 | 360 | }, | |
| 331 | 361 | loadLocal: func(context.Context, string) (routeListSection, error) { | |
| 332 | 362 | return routeListSection{ | |
| @@ -378,11 +408,11 @@ func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t * | |||
| 378 | 408 | ||
| 379 | 409 | func TestCollectRoutesListSections_ExplicitRemoteSkipsAggregate(t *testing.T) { | |
| 380 | 410 | testsDeps := routesListDeps{ | |
| 381 | - | explicitRemote: func() (*remote.ResolvedRemote, bool) { | |
| 411 | + | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { | |
| 382 | 412 | return &remote.ResolvedRemote{ | |
| 383 | 413 | Name: "igor", | |
| 384 | 414 | URL: "https://gordon.supri.xyz", | |
| 385 | - | }, true | |
| 415 | + | }, true, nil | |
| 386 | 416 | }, | |
| 387 | 417 | loadLocal: func(context.Context, string) (routeListSection, error) { | |
| 388 | 418 | t.Fatal("loadLocal should not be called when an explicit remote is selected") | |
| @@ -413,23 +443,7 @@ func TestCollectRoutesListSections_ExplicitRemoteSkipsAggregate(t *testing.T) { | |||
| 413 | 443 | assert.Equal(t, "test.supri.xyz", sections[0].Routes[0].Domain) | |
| 414 | 444 | } | |
| 415 | 445 | ||
| 416 | - | func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t *testing.T) { | |
| 417 | - | originalRemoteFlag := remoteFlag | |
| 418 | - | originalTokenFlag := tokenFlag | |
| 419 | - | originalInsecureTLSFlag := insecureTLSFlag | |
| 420 | - | t.Cleanup(func() { | |
| 421 | - | remoteFlag = originalRemoteFlag | |
| 422 | - | tokenFlag = originalTokenFlag | |
| 423 | - | insecureTLSFlag = originalInsecureTLSFlag | |
| 424 | - | }) | |
| 425 | - | ||
| 426 | - | remoteFlag = "missing-remote" | |
| 427 | - | tokenFlag = "" | |
| 428 | - | insecureTLSFlag = false | |
| 429 | - | configHome := t.TempDir() | |
| 430 | - | t.Setenv("XDG_CONFIG_HOME", configHome) | |
| 431 | - | t.Setenv("HOME", t.TempDir()) | |
| 432 | - | t.Setenv("GORDON_REMOTE", "") | |
| 446 | + | func TestCollectRoutesSections_ExplicitRemoteResolutionErrorReturnsError(t *testing.T) { | |
| 433 | 447 | ||
| 434 | 448 | t.Run("list", func(t *testing.T) { | |
| 435 | 449 | var loadLocalCalls atomic.Int32 | |
| @@ -437,7 +451,9 @@ func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t * | |||
| 437 | 451 | var loadRemoteCalls atomic.Int32 | |
| 438 | 452 | ||
| 439 | 453 | deps := routesListDeps{ | |
| 440 | - | explicitRemote: resolveRoutesExplicitRemote, | |
| 454 | + | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { | |
| 455 | + | return nil, false, errors.New("failed to read remotes: boom") | |
| 456 | + | }, | |
| 441 | 457 | loadLocal: func(context.Context, string) (routeListSection, error) { | |
| 442 | 458 | loadLocalCalls.Add(1) | |
| 443 | 459 | return routeListSection{}, nil | |
| @@ -454,12 +470,9 @@ func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t * | |||
| 454 | 470 | ||
| 455 | 471 | sections, err := collectRoutesListSections(context.Background(), "", deps) | |
| 456 | 472 | ||
| 457 | - | require.NoError(t, err) | |
| 458 | - | require.Len(t, sections, 1) | |
| 459 | - | assert.Equal(t, "remote", sections[0].Kind) | |
| 460 | - | assert.Equal(t, "missing-remote", sections[0].Name) | |
| 461 | - | require.NotEmpty(t, sections[0].Error) | |
| 462 | - | assert.Contains(t, sections[0].Error, "missing-remote") | |
| 473 | + | require.Error(t, err) | |
| 474 | + | require.Nil(t, sections) | |
| 475 | + | assert.Contains(t, err.Error(), "failed to read remotes") | |
| 463 | 476 | assert.Zero(t, loadLocalCalls.Load()) | |
| 464 | 477 | assert.Zero(t, listRemotesCalls.Load()) | |
| 465 | 478 | assert.Zero(t, loadRemoteCalls.Load()) | |
| @@ -471,7 +484,9 @@ func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t * | |||
| 471 | 484 | var loadRemoteCalls atomic.Int32 | |
| 472 | 485 | ||
| 473 | 486 | deps := routesStatusDeps{ | |
| 474 | - | explicitRemote: resolveRoutesExplicitRemote, | |
| 487 | + | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { | |
| 488 | + | return nil, false, errors.New("failed to read remotes: boom") | |
| 489 | + | }, | |
| 475 | 490 | loadLocal: func(context.Context, string) (routeStatusSection, error) { | |
| 476 | 491 | loadLocalCalls.Add(1) | |
| 477 | 492 | return routeStatusSection{}, nil | |
| @@ -488,18 +503,123 @@ func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t * | |||
| 488 | 503 | ||
| 489 | 504 | sections, err := collectRoutesStatusSections(context.Background(), "", deps) | |
| 490 | 505 | ||
| 491 | - | require.NoError(t, err) | |
| 492 | - | require.Len(t, sections, 1) | |
| 493 | - | assert.Equal(t, "remote", sections[0].Kind) | |
| 494 | - | assert.Equal(t, "missing-remote", sections[0].Name) | |
| 495 | - | require.NotEmpty(t, sections[0].Error) | |
| 496 | - | assert.Contains(t, sections[0].Error, "missing-remote") | |
| 506 | + | require.Error(t, err) | |
| 507 | + | require.Nil(t, sections) | |
| 508 | + | assert.Contains(t, err.Error(), "failed to read remotes") | |
| 497 | 509 | assert.Zero(t, loadLocalCalls.Load()) | |
| 498 | 510 | assert.Zero(t, listRemotesCalls.Load()) | |
| 499 | 511 | assert.Zero(t, loadRemoteCalls.Load()) | |
| 500 | 512 | }) | |
| 501 | 513 | } | |
| 502 | 514 | ||
| 515 | + | func TestLoadRoutesListExplicitRemoteSection_PropagatesLoaderError(t *testing.T) { | |
| 516 | + | section := loadRoutesListExplicitRemoteSection(context.Background(), routesListDeps{ | |
| 517 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeListSection, error) { | |
| 518 | + | return routeListSection{}, errors.New("boom") | |
| 519 | + | }, | |
| 520 | + | }, &remote.ResolvedRemote{Name: "igor", URL: "https://gordon.supri.xyz"}) | |
| 521 | + | ||
| 522 | + | assert.Equal(t, "remote", section.Kind) | |
| 523 | + | assert.Equal(t, "igor", section.Name) | |
| 524 | + | assert.Equal(t, "https://gordon.supri.xyz", section.URL) | |
| 525 | + | assert.Equal(t, "boom", section.Error) | |
| 526 | + | } | |
| 527 | + | ||
| 528 | + | func TestLoadRoutesStatusExplicitRemoteSection_PropagatesLoaderError(t *testing.T) { | |
| 529 | + | section := loadRoutesStatusExplicitRemoteSection(context.Background(), routesStatusDeps{ | |
| 530 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) { | |
| 531 | + | return routeStatusSection{}, errors.New("boom") | |
| 532 | + | }, | |
| 533 | + | }, &remote.ResolvedRemote{Name: "igor", URL: "https://gordon.supri.xyz"}) | |
| 534 | + | ||
| 535 | + | assert.Equal(t, "remote", section.Kind) | |
| 536 | + | assert.Equal(t, "igor", section.Name) | |
| 537 | + | assert.Equal(t, "https://gordon.supri.xyz", section.URL) | |
| 538 | + | assert.Equal(t, "boom", section.Error) | |
| 539 | + | } | |
| 540 | + | ||
| 541 | + | func TestCollectRoutesListAggregateSections_EncodesLoaderErrors(t *testing.T) { | |
| 542 | + | t.Run("local loader error", func(t *testing.T) { | |
| 543 | + | sections, err := collectRoutesListAggregateSections(context.Background(), "config.toml", routesListDeps{ | |
| 544 | + | loadLocal: func(context.Context, string) (routeListSection, error) { | |
| 545 | + | return routeListSection{Kind: "local", Name: "local"}, errors.New("local failed") | |
| 546 | + | }, | |
| 547 | + | listRemotes: func() (map[string]remote.RemoteEntry, string, error) { | |
| 548 | + | return map[string]remote.RemoteEntry{}, "", nil | |
| 549 | + | }, | |
| 550 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeListSection, error) { | |
| 551 | + | return routeListSection{Kind: "remote", Name: "igor", URL: "https://gordon.supri.xyz"}, nil | |
| 552 | + | }, | |
| 553 | + | }) | |
| 554 | + | ||
| 555 | + | require.NoError(t, err) | |
| 556 | + | require.Len(t, sections, 1) | |
| 557 | + | assert.Equal(t, "local", sections[0].Kind) | |
| 558 | + | assert.Equal(t, "local failed", sections[0].Error) | |
| 559 | + | }) | |
| 560 | + | ||
| 561 | + | t.Run("remote loader error", func(t *testing.T) { | |
| 562 | + | sections, err := collectRoutesListAggregateSections(context.Background(), "config.toml", routesListDeps{ | |
| 563 | + | loadLocal: func(context.Context, string) (routeListSection, error) { | |
| 564 | + | return routeListSection{Kind: "local", Name: "local"}, nil | |
| 565 | + | }, | |
| 566 | + | listRemotes: func() (map[string]remote.RemoteEntry, string, error) { | |
| 567 | + | return map[string]remote.RemoteEntry{"igor": {URL: "https://gordon.supri.xyz"}}, "", nil | |
| 568 | + | }, | |
| 569 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeListSection, error) { | |
| 570 | + | return routeListSection{Kind: "remote", Name: "igor", URL: "https://gordon.supri.xyz"}, errors.New("remote failed") | |
| 571 | + | }, | |
| 572 | + | }) | |
| 573 | + | ||
| 574 | + | require.NoError(t, err) | |
| 575 | + | require.Len(t, sections, 2) | |
| 576 | + | assert.Equal(t, "local", sections[0].Kind) | |
| 577 | + | assert.Equal(t, "remote", sections[1].Kind) | |
| 578 | + | assert.Equal(t, "remote failed", sections[1].Error) | |
| 579 | + | }) | |
| 580 | + | } | |
| 581 | + | ||
| 582 | + | func TestCollectRoutesStatusAggregateSections_EncodesLoaderErrors(t *testing.T) { | |
| 583 | + | t.Run("local loader error", func(t *testing.T) { | |
| 584 | + | sections, err := collectRoutesStatusAggregateSections(context.Background(), "config.toml", routesStatusDeps{ | |
| 585 | + | loadLocal: func(context.Context, string) (routeStatusSection, error) { | |
| 586 | + | return routeStatusSection{Kind: "local", Name: "local"}, errors.New("local failed") | |
| 587 | + | }, | |
| 588 | + | listRemotes: func() (map[string]remote.RemoteEntry, string, error) { | |
| 589 | + | return map[string]remote.RemoteEntry{}, "", nil | |
| 590 | + | }, | |
| 591 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) { | |
| 592 | + | return routeStatusSection{Kind: "remote", Name: "igor", URL: "https://gordon.supri.xyz"}, nil | |
| 593 | + | }, | |
| 594 | + | }) | |
| 595 | + | ||
| 596 | + | require.NoError(t, err) | |
| 597 | + | require.Len(t, sections, 1) | |
| 598 | + | assert.Equal(t, "local", sections[0].Kind) | |
| 599 | + | assert.Equal(t, "local failed", sections[0].Error) | |
| 600 | + | }) | |
| 601 | + | ||
| 602 | + | t.Run("remote loader error", func(t *testing.T) { | |
| 603 | + | sections, err := collectRoutesStatusAggregateSections(context.Background(), "config.toml", routesStatusDeps{ | |
| 604 | + | loadLocal: func(context.Context, string) (routeStatusSection, error) { | |
| 605 | + | return routeStatusSection{Kind: "local", Name: "local"}, nil | |
| 606 | + | }, | |
| 607 | + | listRemotes: func() (map[string]remote.RemoteEntry, string, error) { | |
| 608 | + | return map[string]remote.RemoteEntry{"igor": {URL: "https://gordon.supri.xyz"}}, "", nil | |
| 609 | + | }, | |
| 610 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) { | |
| 611 | + | return routeStatusSection{Kind: "remote", Name: "igor", URL: "https://gordon.supri.xyz"}, errors.New("remote failed") | |
| 612 | + | }, | |
| 613 | + | }) | |
| 614 | + | ||
| 615 | + | require.NoError(t, err) | |
| 616 | + | require.Len(t, sections, 2) | |
| 617 | + | assert.Equal(t, "local", sections[0].Kind) | |
| 618 | + | assert.Equal(t, "remote", sections[1].Kind) | |
| 619 | + | assert.Equal(t, "remote failed", sections[1].Error) | |
| 620 | + | }) | |
| 621 | + | } | |
| 622 | + | ||
| 503 | 623 | func TestRenderRoutesStatusSections_IncludesSectionHeadingsAndErrors(t *testing.T) { | |
| 504 | 624 | sections := []routeStatusSection{ | |
| 505 | 625 | { | |
| @@ -249,13 +249,43 @@ func TestResolveRoutesExplicitRemote_AllowsAdHocURLWhenSavedRemotesConfigUnreada | |||||
| 249 | configPath := filepath.Join(configHome, "gordon", "remotes.toml") | 249 | configPath := filepath.Join(configHome, "gordon", "remotes.toml") | ||
| 250 | require.NoError(t, os.MkdirAll(configPath, 0o755)) | 250 | require.NoError(t, os.MkdirAll(configPath, 0o755)) | ||
| 251 | 251 | ||||
| 252 | - | resolved, ok := resolveRoutesExplicitRemote() | 252 | + | resolved, ok, err := resolveRoutesExplicitRemote() |
| 253 | require.True(t, ok) | 253 | require.True(t, ok) | ||
| 254 | + | require.NoError(t, err) | |||
| 254 | require.NotNil(t, resolved) | 255 | require.NotNil(t, resolved) | ||
| 255 | assert.Equal(t, "https://ad-hoc.example.com", resolved.URL) | 256 | assert.Equal(t, "https://ad-hoc.example.com", resolved.URL) | ||
| 256 | assert.Empty(t, resolved.Name) | 257 | assert.Empty(t, resolved.Name) | ||
| 257 | } | 258 | } | ||
| 258 | 259 | ||||
| 260 | + | func TestResolveRoutesExplicitRemote_ReturnsErrorForUnreadableNamedRemoteConfig(t *testing.T) { | |||
| 261 | + | originalRemoteFlag := remoteFlag | |||
| 262 | + | originalTokenFlag := tokenFlag | |||
| 263 | + | originalInsecureTLSFlag := insecureTLSFlag | |||
| 264 | + | t.Cleanup(func() { | |||
| 265 | + | remoteFlag = originalRemoteFlag | |||
| 266 | + | tokenFlag = originalTokenFlag | |||
| 267 | + | insecureTLSFlag = originalInsecureTLSFlag | |||
| 268 | + | }) | |||
| 269 | + | ||||
| 270 | + | remoteFlag = "missing-remote" | |||
| 271 | + | tokenFlag = "" | |||
| 272 | + | insecureTLSFlag = false | |||
| 273 | + | ||||
| 274 | + | configHome := t.TempDir() | |||
| 275 | + | t.Setenv("XDG_CONFIG_HOME", configHome) | |||
| 276 | + | t.Setenv("HOME", t.TempDir()) | |||
| 277 | + | t.Setenv("GORDON_REMOTE", "") | |||
| 278 | + | ||||
| 279 | + | configPath := filepath.Join(configHome, "gordon", "remotes.toml") | |||
| 280 | + | require.NoError(t, os.MkdirAll(configPath, 0o755)) | |||
| 281 | + | ||||
| 282 | + | resolved, ok, err := resolveRoutesExplicitRemote() | |||
| 283 | + | require.Error(t, err) | |||
| 284 | + | require.False(t, ok) | |||
| 285 | + | require.Nil(t, resolved) | |||
| 286 | + | assert.Contains(t, err.Error(), "failed to read remotes") | |||
| 287 | + | } | |||
| 288 | + | ||||
| 259 | func TestRouteStatusTitle_PreservesProbeFailureError(t *testing.T) { | 289 | func TestRouteStatusTitle_PreservesProbeFailureError(t *testing.T) { | ||
| 260 | item := routeStatusItem{ | 290 | item := routeStatusItem{ | ||
| 261 | Domain: "app.example.com", | 291 | Domain: "app.example.com", | ||
| @@ -325,8 +355,8 @@ func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | |||||
| 325 | 355 | ||||
| 326 | func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) { | 356 | func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) { | ||
| 327 | testsDeps := routesListDeps{ | 357 | testsDeps := routesListDeps{ | ||
| 328 | - | explicitRemote: func() (*remote.ResolvedRemote, bool) { | 358 | + | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { |
| 329 | - | return nil, false | 359 | + | return nil, false, nil |
| 330 | }, | 360 | }, | ||
| 331 | loadLocal: func(context.Context, string) (routeListSection, error) { | 361 | loadLocal: func(context.Context, string) (routeListSection, error) { | ||
| 332 | return routeListSection{ | 362 | return routeListSection{ | ||
| @@ -378,11 +408,11 @@ func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t * | |||||
| 378 | 408 | ||||
| 379 | func TestCollectRoutesListSections_ExplicitRemoteSkipsAggregate(t *testing.T) { | 409 | func TestCollectRoutesListSections_ExplicitRemoteSkipsAggregate(t *testing.T) { | ||
| 380 | testsDeps := routesListDeps{ | 410 | testsDeps := routesListDeps{ | ||
| 381 | - | explicitRemote: func() (*remote.ResolvedRemote, bool) { | 411 | + | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { |
| 382 | return &remote.ResolvedRemote{ | 412 | return &remote.ResolvedRemote{ | ||
| 383 | Name: "igor", | 413 | Name: "igor", | ||
| 384 | URL: "https://gordon.supri.xyz", | 414 | URL: "https://gordon.supri.xyz", | ||
| 385 | - | }, true | 415 | + | }, true, nil |
| 386 | }, | 416 | }, | ||
| 387 | loadLocal: func(context.Context, string) (routeListSection, error) { | 417 | loadLocal: func(context.Context, string) (routeListSection, error) { | ||
| 388 | t.Fatal("loadLocal should not be called when an explicit remote is selected") | 418 | t.Fatal("loadLocal should not be called when an explicit remote is selected") | ||
| @@ -413,23 +443,7 @@ func TestCollectRoutesListSections_ExplicitRemoteSkipsAggregate(t *testing.T) { | |||||
| 413 | assert.Equal(t, "test.supri.xyz", sections[0].Routes[0].Domain) | 443 | assert.Equal(t, "test.supri.xyz", sections[0].Routes[0].Domain) | ||
| 414 | } | 444 | } | ||
| 415 | 445 | ||||
| 416 | - | func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t *testing.T) { | 446 | + | func TestCollectRoutesSections_ExplicitRemoteResolutionErrorReturnsError(t *testing.T) { |
| 417 | - | originalRemoteFlag := remoteFlag | |||
| 418 | - | originalTokenFlag := tokenFlag | |||
| 419 | - | originalInsecureTLSFlag := insecureTLSFlag | |||
| 420 | - | t.Cleanup(func() { | |||
| 421 | - | remoteFlag = originalRemoteFlag | |||
| 422 | - | tokenFlag = originalTokenFlag | |||
| 423 | - | insecureTLSFlag = originalInsecureTLSFlag | |||
| 424 | - | }) | |||
| 425 | - | ||||
| 426 | - | remoteFlag = "missing-remote" | |||
| 427 | - | tokenFlag = "" | |||
| 428 | - | insecureTLSFlag = false | |||
| 429 | - | configHome := t.TempDir() | |||
| 430 | - | t.Setenv("XDG_CONFIG_HOME", configHome) | |||
| 431 | - | t.Setenv("HOME", t.TempDir()) | |||
| 432 | - | t.Setenv("GORDON_REMOTE", "") | |||
| 433 | 447 | ||||
| 434 | t.Run("list", func(t *testing.T) { | 448 | t.Run("list", func(t *testing.T) { | ||
| 435 | var loadLocalCalls atomic.Int32 | 449 | var loadLocalCalls atomic.Int32 | ||
| @@ -437,7 +451,9 @@ func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t * | |||||
| 437 | var loadRemoteCalls atomic.Int32 | 451 | var loadRemoteCalls atomic.Int32 | ||
| 438 | 452 | ||||
| 439 | deps := routesListDeps{ | 453 | deps := routesListDeps{ | ||
| 440 | - | explicitRemote: resolveRoutesExplicitRemote, | 454 | + | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { |
| 455 | + | return nil, false, errors.New("failed to read remotes: boom") | |||
| 456 | + | }, | |||
| 441 | loadLocal: func(context.Context, string) (routeListSection, error) { | 457 | loadLocal: func(context.Context, string) (routeListSection, error) { | ||
| 442 | loadLocalCalls.Add(1) | 458 | loadLocalCalls.Add(1) | ||
| 443 | return routeListSection{}, nil | 459 | return routeListSection{}, nil | ||
| @@ -454,12 +470,9 @@ func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t * | |||||
| 454 | 470 | ||||
| 455 | sections, err := collectRoutesListSections(context.Background(), "", deps) | 471 | sections, err := collectRoutesListSections(context.Background(), "", deps) | ||
| 456 | 472 | ||||
| 457 | - | require.NoError(t, err) | 473 | + | require.Error(t, err) |
| 458 | - | require.Len(t, sections, 1) | 474 | + | require.Nil(t, sections) |
| 459 | - | assert.Equal(t, "remote", sections[0].Kind) | 475 | + | assert.Contains(t, err.Error(), "failed to read remotes") |
| 460 | - | assert.Equal(t, "missing-remote", sections[0].Name) | |||
| 461 | - | require.NotEmpty(t, sections[0].Error) | |||
| 462 | - | assert.Contains(t, sections[0].Error, "missing-remote") | |||
| 463 | assert.Zero(t, loadLocalCalls.Load()) | 476 | assert.Zero(t, loadLocalCalls.Load()) | ||
| 464 | assert.Zero(t, listRemotesCalls.Load()) | 477 | assert.Zero(t, listRemotesCalls.Load()) | ||
| 465 | assert.Zero(t, loadRemoteCalls.Load()) | 478 | assert.Zero(t, loadRemoteCalls.Load()) | ||
| @@ -471,7 +484,9 @@ func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t * | |||||
| 471 | var loadRemoteCalls atomic.Int32 | 484 | var loadRemoteCalls atomic.Int32 | ||
| 472 | 485 | ||||
| 473 | deps := routesStatusDeps{ | 486 | deps := routesStatusDeps{ | ||
| 474 | - | explicitRemote: resolveRoutesExplicitRemote, | 487 | + | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { |
| 488 | + | return nil, false, errors.New("failed to read remotes: boom") | |||
| 489 | + | }, | |||
| 475 | loadLocal: func(context.Context, string) (routeStatusSection, error) { | 490 | loadLocal: func(context.Context, string) (routeStatusSection, error) { | ||
| 476 | loadLocalCalls.Add(1) | 491 | loadLocalCalls.Add(1) | ||
| 477 | return routeStatusSection{}, nil | 492 | return routeStatusSection{}, nil | ||
| @@ -488,18 +503,123 @@ func TestCollectRoutesSections_ExplicitUnknownNamedTargetReturnsErrorSection(t * | |||||
| 488 | 503 | ||||
| 489 | sections, err := collectRoutesStatusSections(context.Background(), "", deps) | 504 | sections, err := collectRoutesStatusSections(context.Background(), "", deps) | ||
| 490 | 505 | ||||
| 491 | - | require.NoError(t, err) | 506 | + | require.Error(t, err) |
| 492 | - | require.Len(t, sections, 1) | 507 | + | require.Nil(t, sections) |
| 493 | - | assert.Equal(t, "remote", sections[0].Kind) | 508 | + | assert.Contains(t, err.Error(), "failed to read remotes") |
| 494 | - | assert.Equal(t, "missing-remote", sections[0].Name) | |||
| 495 | - | require.NotEmpty(t, sections[0].Error) | |||
| 496 | - | assert.Contains(t, sections[0].Error, "missing-remote") | |||
| 497 | assert.Zero(t, loadLocalCalls.Load()) | 509 | assert.Zero(t, loadLocalCalls.Load()) | ||
| 498 | assert.Zero(t, listRemotesCalls.Load()) | 510 | assert.Zero(t, listRemotesCalls.Load()) | ||
| 499 | assert.Zero(t, loadRemoteCalls.Load()) | 511 | assert.Zero(t, loadRemoteCalls.Load()) | ||
| 500 | }) | 512 | }) | ||
| 501 | } | 513 | } | ||
| 502 | 514 | ||||
| 515 | + | func TestLoadRoutesListExplicitRemoteSection_PropagatesLoaderError(t *testing.T) { | |||
| 516 | + | section := loadRoutesListExplicitRemoteSection(context.Background(), routesListDeps{ | |||
| 517 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeListSection, error) { | |||
| 518 | + | return routeListSection{}, errors.New("boom") | |||
| 519 | + | }, | |||
| 520 | + | }, &remote.ResolvedRemote{Name: "igor", URL: "https://gordon.supri.xyz"}) | |||
| 521 | + | ||||
| 522 | + | assert.Equal(t, "remote", section.Kind) | |||
| 523 | + | assert.Equal(t, "igor", section.Name) | |||
| 524 | + | assert.Equal(t, "https://gordon.supri.xyz", section.URL) | |||
| 525 | + | assert.Equal(t, "boom", section.Error) | |||
| 526 | + | } | |||
| 527 | + | ||||
| 528 | + | func TestLoadRoutesStatusExplicitRemoteSection_PropagatesLoaderError(t *testing.T) { | |||
| 529 | + | section := loadRoutesStatusExplicitRemoteSection(context.Background(), routesStatusDeps{ | |||
| 530 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) { | |||
| 531 | + | return routeStatusSection{}, errors.New("boom") | |||
| 532 | + | }, | |||
| 533 | + | }, &remote.ResolvedRemote{Name: "igor", URL: "https://gordon.supri.xyz"}) | |||
| 534 | + | ||||
| 535 | + | assert.Equal(t, "remote", section.Kind) | |||
| 536 | + | assert.Equal(t, "igor", section.Name) | |||
| 537 | + | assert.Equal(t, "https://gordon.supri.xyz", section.URL) | |||
| 538 | + | assert.Equal(t, "boom", section.Error) | |||
| 539 | + | } | |||
| 540 | + | ||||
| 541 | + | func TestCollectRoutesListAggregateSections_EncodesLoaderErrors(t *testing.T) { | |||
| 542 | + | t.Run("local loader error", func(t *testing.T) { | |||
| 543 | + | sections, err := collectRoutesListAggregateSections(context.Background(), "config.toml", routesListDeps{ | |||
| 544 | + | loadLocal: func(context.Context, string) (routeListSection, error) { | |||
| 545 | + | return routeListSection{Kind: "local", Name: "local"}, errors.New("local failed") | |||
| 546 | + | }, | |||
| 547 | + | listRemotes: func() (map[string]remote.RemoteEntry, string, error) { | |||
| 548 | + | return map[string]remote.RemoteEntry{}, "", nil | |||
| 549 | + | }, | |||
| 550 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeListSection, error) { | |||
| 551 | + | return routeListSection{Kind: "remote", Name: "igor", URL: "https://gordon.supri.xyz"}, nil | |||
| 552 | + | }, | |||
| 553 | + | }) | |||
| 554 | + | ||||
| 555 | + | require.NoError(t, err) | |||
| 556 | + | require.Len(t, sections, 1) | |||
| 557 | + | assert.Equal(t, "local", sections[0].Kind) | |||
| 558 | + | assert.Equal(t, "local failed", sections[0].Error) | |||
| 559 | + | }) | |||
| 560 | + | ||||
| 561 | + | t.Run("remote loader error", func(t *testing.T) { | |||
| 562 | + | sections, err := collectRoutesListAggregateSections(context.Background(), "config.toml", routesListDeps{ | |||
| 563 | + | loadLocal: func(context.Context, string) (routeListSection, error) { | |||
| 564 | + | return routeListSection{Kind: "local", Name: "local"}, nil | |||
| 565 | + | }, | |||
| 566 | + | listRemotes: func() (map[string]remote.RemoteEntry, string, error) { | |||
| 567 | + | return map[string]remote.RemoteEntry{"igor": {URL: "https://gordon.supri.xyz"}}, "", nil | |||
| 568 | + | }, | |||
| 569 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeListSection, error) { | |||
| 570 | + | return routeListSection{Kind: "remote", Name: "igor", URL: "https://gordon.supri.xyz"}, errors.New("remote failed") | |||
| 571 | + | }, | |||
| 572 | + | }) | |||
| 573 | + | ||||
| 574 | + | require.NoError(t, err) | |||
| 575 | + | require.Len(t, sections, 2) | |||
| 576 | + | assert.Equal(t, "local", sections[0].Kind) | |||
| 577 | + | assert.Equal(t, "remote", sections[1].Kind) | |||
| 578 | + | assert.Equal(t, "remote failed", sections[1].Error) | |||
| 579 | + | }) | |||
| 580 | + | } | |||
| 581 | + | ||||
| 582 | + | func TestCollectRoutesStatusAggregateSections_EncodesLoaderErrors(t *testing.T) { | |||
| 583 | + | t.Run("local loader error", func(t *testing.T) { | |||
| 584 | + | sections, err := collectRoutesStatusAggregateSections(context.Background(), "config.toml", routesStatusDeps{ | |||
| 585 | + | loadLocal: func(context.Context, string) (routeStatusSection, error) { | |||
| 586 | + | return routeStatusSection{Kind: "local", Name: "local"}, errors.New("local failed") | |||
| 587 | + | }, | |||
| 588 | + | listRemotes: func() (map[string]remote.RemoteEntry, string, error) { | |||
| 589 | + | return map[string]remote.RemoteEntry{}, "", nil | |||
| 590 | + | }, | |||
| 591 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) { | |||
| 592 | + | return routeStatusSection{Kind: "remote", Name: "igor", URL: "https://gordon.supri.xyz"}, nil | |||
| 593 | + | }, | |||
| 594 | + | }) | |||
| 595 | + | ||||
| 596 | + | require.NoError(t, err) | |||
| 597 | + | require.Len(t, sections, 1) | |||
| 598 | + | assert.Equal(t, "local", sections[0].Kind) | |||
| 599 | + | assert.Equal(t, "local failed", sections[0].Error) | |||
| 600 | + | }) | |||
| 601 | + | ||||
| 602 | + | t.Run("remote loader error", func(t *testing.T) { | |||
| 603 | + | sections, err := collectRoutesStatusAggregateSections(context.Background(), "config.toml", routesStatusDeps{ | |||
| 604 | + | loadLocal: func(context.Context, string) (routeStatusSection, error) { | |||
| 605 | + | return routeStatusSection{Kind: "local", Name: "local"}, nil | |||
| 606 | + | }, | |||
| 607 | + | listRemotes: func() (map[string]remote.RemoteEntry, string, error) { | |||
| 608 | + | return map[string]remote.RemoteEntry{"igor": {URL: "https://gordon.supri.xyz"}}, "", nil | |||
| 609 | + | }, | |||
| 610 | + | loadRemote: func(context.Context, string, remote.RemoteEntry) (routeStatusSection, error) { | |||
| 611 | + | return routeStatusSection{Kind: "remote", Name: "igor", URL: "https://gordon.supri.xyz"}, errors.New("remote failed") | |||
| 612 | + | }, | |||
| 613 | + | }) | |||
| 614 | + | ||||
| 615 | + | require.NoError(t, err) | |||
| 616 | + | require.Len(t, sections, 2) | |||
| 617 | + | assert.Equal(t, "local", sections[0].Kind) | |||
| 618 | + | assert.Equal(t, "remote", sections[1].Kind) | |||
| 619 | + | assert.Equal(t, "remote failed", sections[1].Error) | |||
| 620 | + | }) | |||
| 621 | + | } | |||
| 622 | + | ||||
| 503 | func TestRenderRoutesStatusSections_IncludesSectionHeadingsAndErrors(t *testing.T) { | 623 | func TestRenderRoutesStatusSections_IncludesSectionHeadingsAndErrors(t *testing.T) { | ||
| 504 | sections := []routeStatusSection{ | 624 | sections := []routeStatusSection{ | ||
| 505 | { | 625 | { | ||