FailureModeTests.cs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. using RackPeek.Domain.Discovery;
  2. namespace Tests.Discovery;
  3. /// <summary>
  4. /// Discovery runs unattended on machines nobody is watching, so its failures have to
  5. /// be legible from a log line rather than a stack trace.
  6. /// </summary>
  7. public class FailureModeTests {
  8. [Fact]
  9. public async Task An_unreachable_server_fails_with_a_network_error_not_a_crash() {
  10. // Port 1 is reserved and nothing listens on it.
  11. using var publisher = new DiscoveryPublisher("http://127.0.0.1:1", "key");
  12. await Assert.ThrowsAnyAsync<HttpRequestException>(
  13. () => publisher.PublishAsync("version: 3\nresources: []\n", false));
  14. }
  15. [Fact]
  16. public async Task An_unreachable_docker_socket_reports_where_it_looked() {
  17. using var client = new DockerApiClient("unix:///tmp/definitely-not-a-docker.sock");
  18. Assert.Equal("unix:///tmp/definitely-not-a-docker.sock", client.Endpoint);
  19. await Assert.ThrowsAnyAsync<Exception>(() => client.ListContainersAsync());
  20. }
  21. [Fact]
  22. public void The_default_docker_endpoint_is_the_conventional_socket() {
  23. using var client = new DockerApiClient();
  24. // DOCKER_HOST wins when set, which is how the remote and Podman cases work.
  25. Assert.Contains("docker.sock", client.Endpoint, StringComparison.Ordinal);
  26. }
  27. [Theory]
  28. [InlineData(null)]
  29. [InlineData("")]
  30. [InlineData(" ")]
  31. public void A_missing_server_or_key_resolves_to_nothing_so_validation_can_catch_it(string? value) {
  32. Assert.Null(DiscoveryPublisher.ResolveServer(value)
  33. ?? Environment.GetEnvironmentVariable(DiscoveryPublisher.ServerEnvironmentVariable));
  34. }
  35. [Fact]
  36. public void Garbage_from_the_docker_api_does_not_take_the_process_down() =>
  37. Assert.ThrowsAny<Exception>(() => DockerContainerParser.Parse("not json at all"));
  38. [Fact]
  39. public void A_container_with_no_name_is_skipped_rather_than_named_badly() {
  40. List<DockerContainer> containers = DockerContainerParser.Parse("""
  41. [
  42. { "Id": "abc", "Image": "x", "Ports": [] },
  43. { "Id": "def", "Names": ["/real"], "Image": "y",
  44. "Ports": [{ "PrivatePort": 80, "PublicPort": 80, "Type": "tcp" }] }
  45. ]
  46. """);
  47. Assert.Equal(["real"], containers.Select(c => c.Name));
  48. }
  49. [Fact]
  50. public async Task A_push_against_an_unreadable_config_fails_rather_than_overwriting_it() {
  51. // The server tolerates a malformed config at boot so the web UI can be used to
  52. // fix it. A push arriving in that state must fail — merging against the empty
  53. // in-memory collection and saving would replace the user's whole inventory
  54. // with just the pushed resources.
  55. const string malformed = "version: 3\nresources:\n - kind: [not yaml";
  56. using var api = new DiscoveryApiFixture(malformed);
  57. SystemFacts facts = SystemFactsParser.Parse(new RawSystemSnapshot {
  58. Hostname = "pusher",
  59. Cores = 2,
  60. OsName = "Debian",
  61. MemoryBytes = 4L * 1024 * 1024 * 1024,
  62. PlatformUuid = "uuid"
  63. });
  64. await Assert.ThrowsAsync<InvalidOperationException>(() =>
  65. api.PublishAsync(DiscoveryDocument.ToYaml([SystemResourceMapper.ToResource(facts)])));
  66. Assert.Equal(malformed, api.StoredYaml);
  67. }
  68. [Fact]
  69. public void A_machine_with_no_network_still_produces_an_importable_resource() {
  70. SystemFacts facts = SystemFactsParser.Parse(new RawSystemSnapshot {
  71. Hostname = "offline-box",
  72. Cores = 2,
  73. OsName = "Debian",
  74. MemoryBytes = 4L * 1024 * 1024 * 1024,
  75. PlatformUuid = "uuid"
  76. });
  77. Assert.Null(facts.Ip);
  78. // ip is optional in the schema; type, os, cores and ram are not.
  79. Fixture.AssertConformsToSchema(
  80. DiscoveryDocument.ToYaml([SystemResourceMapper.ToResource(facts)]));
  81. }
  82. }