| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- using RackPeek.Domain.Api;
- using RackPeek.Domain.Discovery;
- using RackPeek.Domain.Resources;
- using RackPeek.Domain.Resources.SystemResources;
- namespace Tests.Discovery;
- /// <summary>
- /// Network-scan output through the real server: pushed over HTTP, merged by the
- /// real resolver, asserted against what lands on disk. These pin the identity
- /// contracts a scan lives or dies by — idempotent re-runs, renames that stick,
- /// and never stealing the identity of a host another collector documented.
- /// </summary>
- public class NetworkDiscoveryMergeTests {
- private static string ScanYaml(params NetworkHostFact[] hosts) =>
- DiscoveryDocument.ToYaml(NetworkScanMapper.ToResources(hosts));
- private static NetworkHostFact Nas(string ip = "192.168.1.20") =>
- new(ip, "dc:a6:32:0f:11:22", "nas01.lan", true, []);
- [Fact]
- public async Task A_scan_lands_on_disk_and_a_rescan_changes_nothing() {
- using var api = new DiscoveryApiFixture();
- ImportYamlResponse first = await api.PublishAsync(ScanYaml(Nas()));
- Assert.Equal(["nas01"], first.Added);
- Assert.Contains("discoveryId: rpk1:net:", api.StoredYaml);
- Assert.Contains("mac: dc:a6:32:0f:11:22", api.StoredYaml);
- Fixture.AssertConformsToSchema(api.StoredYaml);
- ImportYamlResponse second = await api.PublishAsync(ScanYaml(Nas()));
- Assert.Empty(second.Added);
- Assert.Empty(second.Updated);
- }
- [Fact]
- public async Task A_users_rename_survives_the_next_scan() {
- // The stored card is a previous scan of the same machine that the user has
- // since renamed — the id stayed with it, as the UI keeps it on a rename.
- List<Resource> renamed = NetworkScanMapper.ToResources([Nas()]);
- renamed[0].Name = "storage-primary";
- using var api = new DiscoveryApiFixture(DiscoveryDocument.ToYaml(renamed));
- ImportYamlResponse rescan = await api.PublishAsync(ScanYaml(Nas()));
- Assert.Empty(rescan.Added);
- Assert.Contains("storage-primary", api.StoredYaml);
- Assert.DoesNotContain("name: nas01", api.StoredYaml);
- }
- [Fact]
- public async Task A_dhcp_move_updates_the_address_of_the_same_machine() {
- using var api = new DiscoveryApiFixture();
- await api.PublishAsync(ScanYaml(Nas(ip: "192.168.1.20")));
- ImportYamlResponse moved = await api.PublishAsync(ScanYaml(Nas(ip: "192.168.1.99")));
- Assert.Empty(moved.Added); // same MAC, same machine
- Assert.Equal(["nas01"], moved.Updated);
- Assert.Contains("ip: 192.168.1.99", api.StoredYaml);
- Assert.DoesNotContain("192.168.1.20", api.StoredYaml);
- }
- [Fact]
- public async Task A_scan_never_steals_the_identity_of_an_agent_discovered_host() {
- // nas01 exists with a machine-id identity but WITHOUT the macs label an agent
- // records (an older agent, or a hand-stripped label) — so there is no MAC
- // bridge, and the resolver must keep the cards apart rather than guess.
- var agentDiscovered = DiscoveryDocument.ToYaml([
- new SystemResource {
- Kind = SystemResource.KindLabel,
- Name = "nas01",
- DiscoveryId = DiscoveryId.Create(DiscoveryId.SystemScheme, "machine-a"),
- Type = "baremetal",
- Os = "Debian",
- Cores = 12
- }
- ]);
- using var api = new DiscoveryApiFixture(agentDiscovered);
- ImportYamlResponse response = await api.PublishAsync(ScanYaml(Nas()));
- var scanName = Assert.Single(response.Added);
- Assert.StartsWith("nas01-", scanName); // suffixed, not adopted
- var stored = api.StoredYaml;
- Assert.Contains("rpk1:sys:", stored); // the agent identity is intact
- Assert.Contains("rpk1:net:", stored); // and the scan's card exists beside it
- Assert.Contains("os: Debian", stored); // nothing on the original was touched
- }
- [Fact]
- public async Task An_agent_claims_a_scanned_card_and_every_collector_lands_on_it_after() {
- // The unification headline, scan-first: the sweep found the box, then
- // `rpk discover system` runs on it. Same MAC, so it is the same card — the
- // agent's identity and detail land on the scan's card instead of duplicating.
- using var api = new DiscoveryApiFixture();
- await api.PublishAsync(ScanYaml(Nas()));
- SystemResource agent = SystemResourceMapper.ToResource(new SystemFacts {
- Hostname = "nas01.lan",
- MachineId = "machine-a",
- Os = "Debian 12",
- Cores = 12,
- RamGb = 64,
- Type = "baremetal",
- Ip = "192.168.1.20",
- Macs = ["dc:a6:32:0f:11:22"]
- });
- ImportYamlResponse claim = await api.PublishAsync(DiscoveryDocument.ToYaml([agent]));
- // Nothing added: the agent updated the scan's card (which keeps its name).
- Assert.Empty(claim.Added);
- Assert.Equal(["nas01"], claim.Updated);
- var stored = api.StoredYaml;
- Assert.Contains("rpk1:sys:", stored); // identity upgraded to the agent's
- Assert.DoesNotContain("rpk1:net:", stored);
- Assert.Contains("os: Debian 12", stored);
- Assert.Contains("mac: dc:a6:32:0f:11:22", stored);
- Assert.Contains("macs: dc:a6:32:0f:11:22", stored);
- Fixture.AssertConformsToSchema(stored);
- // ...and a rescan afterwards still lands on that same card via the MAC.
- ImportYamlResponse rescan = await api.PublishAsync(ScanYaml(Nas(ip: "192.168.1.99")));
- Assert.Empty(rescan.Added);
- Assert.Contains("rpk1:sys:", api.StoredYaml); // never downgraded
- Assert.Contains("ip: 192.168.1.99", api.StoredYaml); // but freshly addressed
- }
- [Fact]
- public async Task A_scan_enriches_an_agent_discovered_card_instead_of_duplicating_it() {
- // The reverse order: the agent documented the box first, then a sweep sees it.
- SystemResource agent = SystemResourceMapper.ToResource(new SystemFacts {
- Hostname = "nas01",
- MachineId = "machine-a",
- Os = "Debian 12",
- Cores = 12,
- RamGb = 64,
- Type = "baremetal",
- Macs = ["dc:a6:32:0f:11:22"]
- });
- using var api = new DiscoveryApiFixture(DiscoveryDocument.ToYaml([agent]));
- ImportYamlResponse scan = await api.PublishAsync(ScanYaml(Nas()));
- Assert.Empty(scan.Added);
- Assert.Equal(["nas01"], scan.Updated);
- var stored = api.StoredYaml;
- Assert.Contains("rpk1:sys:", stored); // the agent identity is untouched
- Assert.DoesNotContain("rpk1:net:", stored);
- Assert.Contains("ip: 192.168.1.20", stored); // the scan contributed the address
- Assert.Contains("os: Debian 12", stored);
- Fixture.AssertConformsToSchema(stored);
- }
- [Fact]
- public async Task A_scan_adopts_a_hand_written_system_of_the_same_name() {
- // The inverse case: the user typed the card themselves, so it has no id yet.
- // The scan stamps its identity onto it and enriches it instead of duplicating.
- using var api = new DiscoveryApiFixture(
- """
- version: 4
- resources:
- - kind: System
- name: nas01
- type: baremetal
- os: Debian
- """);
- ImportYamlResponse response = await api.PublishAsync(ScanYaml(Nas()));
- Assert.Empty(response.Added);
- Assert.Equal(["nas01"], response.Updated);
- var stored = api.StoredYaml;
- Assert.Contains("rpk1:net:", stored);
- Assert.Contains("ip: 192.168.1.20", stored);
- // The scan card is sparse on purpose, so everything the user wrote survives.
- Assert.Contains("os: Debian", stored);
- Assert.Contains("type: baremetal", stored);
- }
- }
|