import pytest import gen_rack def item(**kw): base = {"hostname": "x", "kind": "server", "status": "in-use", "rack": "rack01"} base.update(kw) return base def shelf(**kw): base = {"hostname": "shf01", "kind": "shelf", "status": "in-use", "rack": "rack01", "rack_u": 37, "u_height": 10, "rack_face": "both"} base.update(kw) return base def test_validate_accepts_valid_placement(): gen_rack.validate_item(item(rack_u=12, u_height=2, rack_face="front")) def test_validate_rejects_u_overflow(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(rack_u=47, u_height=3, rack_face="front")) def test_validate_rejects_u_below_one(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(rack_u=0, u_height=1, rack_face="front")) def test_validate_rejects_bad_face(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(rack_u=1, u_height=1, rack_face="sideways")) def test_validate_rejects_zero_u_with_units(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(rack_face="left", rack_u=1, u_height=1)) def test_validate_accepts_zero_u_rail(): gen_rack.validate_item(item(rack_face="left")) def test_validate_rejects_missing_units_on_faced_item(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(rack_face="front")) def test_validate_rejects_empty_rack(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(rack=None, rack_u=1, u_height=1, rack_face="front")) def test_overlaps_detects_same_face_overlap(): items = [ item(hostname="a", rack_u=1, u_height=2, rack_face="front"), item(hostname="b", rack_u=2, u_height=1, rack_face="front"), ] with pytest.raises(gen_rack.SchemaError): gen_rack.check_overlaps(items) def test_overlaps_allows_same_u_different_face(): items = [ item(hostname="a", rack_u=5, u_height=1, rack_face="front"), item(hostname="b", rack_u=5, u_height=1, rack_face="rear"), ] gen_rack.check_overlaps(items) # no raise def test_overlaps_both_face_conflicts_with_front(): items = [ item(hostname="a", rack_u=5, u_height=1, rack_face="both"), item(hostname="b", rack_u=5, u_height=1, rack_face="front"), ] with pytest.raises(gen_rack.SchemaError): gen_rack.check_overlaps(items) def test_overlaps_ignores_zero_u_rails(): items = [ item(hostname="p1", rack_face="left"), item(hostname="p2", rack_face="left"), ] gen_rack.check_overlaps(items) # no raise def test_render_svg_has_two_columns_of_48_slots(): svg = gen_rack.render_svg("rack01", []) # one faint slot rect per U per column (front + rear) assert svg.count('fill="#f5f5f5"') == 2 * gen_rack.RACK_UNITS assert svg.startswith("") def test_render_svg_includes_device_label(): items = [item(hostname="mf00", rack_u=1, u_height=2, rack_face="front")] svg = gen_rack.render_svg("rack01", items) assert "mf00" in svg assert "U1" in svg def test_render_svg_is_deterministic(): items = [ item(hostname="b", rack_u=3, u_height=1, rack_face="front"), item(hostname="a", rack_u=1, u_height=1, rack_face="rear"), ] assert gen_rack.render_svg("rack01", items) == gen_rack.render_svg( "rack01", list(reversed(items)) ) def test_render_page_has_banner_image_and_table(): items = [item(hostname="mf00", rack_u=1, u_height=2, rack_face="front")] page = gen_rack.render_page("rack01", items) assert "do not edit by hand" in page assert '
' in page assert "|outlet") == 2 def test_render_power_empty_when_no_feeds(): items = [item(hostname="mf00", rack_u=1, u_height=1, rack_face="front")] assert gen_rack.render_power("rack01", items) == "" def test_render_power_is_deterministic(): a = item(hostname="pdu01", kind="pdu", rack_face="left", outlets=8) b = item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", power=[{"pdu": "pdu01", "outlet": 2}]) c = item(hostname="mf00", rack_u=1, u_height=1, rack_face="front", power=[{"pdu": "pdu01", "outlet": 1}]) assert gen_rack.render_power("rack01", [a, b, c]) == \ gen_rack.render_power("rack01", [c, b, a]) def test_generate_includes_power_section(tmp_path): hw = tmp_path / "hardware" out = tmp_path / "out" hw.mkdir() _write_item( hw, "pdu01", "---\nhostname: pdu01\nkind: pdu\nstatus: in-use\n" "rack: rack01\nrack_face: left\noutlets: 8\n---\n", ) _write_item( hw, "mf00", "---\nhostname: mf00\nkind: server\nstatus: in-use\n" "rack: rack01\nrack_u: 1\nu_height: 1\nrack_face: front\n" "power:\n - { pdu: pdu01, outlet: 1 }\n---\n", ) rc = gen_rack.generate(hw, out) assert rc == 0 page = (out / "rack01.md").read_text() assert "## Power" in page assert "```mermaid" in page assert "outlet 1" in page def test_load_hardware_index_maps_all_hostnames(tmp_path): hw = tmp_path / "hardware" hw.mkdir() _write_item( hw, "sw01", "---\nhostname: sw01\nkind: switch\nstatus: in-use\nports: 24\n---\n", ) _write_item( hw, "mf00", "---\nhostname: mf00\nkind: server\nstatus: in-use\n" "rack: rack01\nrack_u: 1\nu_height: 1\nrack_face: front\n---\n", ) idx = gen_rack.load_hardware_index(hw) assert set(idx) == {"sw01", "mf00"} assert idx["sw01"]["ports"] == 24 def test_validate_links_accepts_valid_link(): items = [item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "sw01", "peer_port": 1, "speed_gbps": 1}])] hw_index = {"sw01": item(hostname="sw01", kind="switch", ports=24)} gen_rack.validate_links(items, hw_index) def test_validate_links_rejects_unknown_peer(): items = [item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "ghost", "peer_port": 1}])] with pytest.raises(gen_rack.SchemaError): gen_rack.validate_links(items, {}) def test_validate_links_rejects_peer_port_over_count(): items = [item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "sw01", "peer_port": 25}])] hw_index = {"sw01": item(hostname="sw01", kind="switch", ports=24)} with pytest.raises(gen_rack.SchemaError): gen_rack.validate_links(items, hw_index) def test_validate_links_rejects_peer_port_below_one(): items = [item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "sw01", "peer_port": 0}])] hw_index = {"sw01": item(hostname="sw01", kind="switch", ports=24)} with pytest.raises(gen_rack.SchemaError): gen_rack.validate_links(items, hw_index) def test_validate_links_accepts_peer_without_ports(): items = [item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "rtr01", "peer_port": 99}])] hw_index = {"rtr01": item(hostname="rtr01", kind="server")} gen_rack.validate_links(items, hw_index) # no ports -> range check skipped def test_validate_links_rejects_missing_local(): items = [item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=[{"peer": "sw01", "peer_port": 1}])] hw_index = {"sw01": item(hostname="sw01", kind="switch", ports=24)} with pytest.raises(gen_rack.SchemaError): gen_rack.validate_links(items, hw_index) def test_validate_links_rejects_malformed_entry(): items = [item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=["sw01"])] with pytest.raises(gen_rack.SchemaError): gen_rack.validate_links(items, {}) def test_generate_returns_1_on_bad_link_peer(tmp_path): hw = tmp_path / "hardware" out = tmp_path / "out" hw.mkdir() _write_item( hw, "mf00", "---\nhostname: mf00\nkind: server\nstatus: in-use\n" "rack: rack01\nrack_u: 1\nu_height: 1\nrack_face: front\n" "links:\n - { local: eth0, peer: ghost, peer_port: 1 }\n---\n", ) rc = gen_rack.generate(hw, out) assert rc == 1 assert not (out / "rack01.md").exists() def test_render_network_has_nodes_and_edge_labels(): items = [ item(hostname="sw01", kind="switch", rack_u=10, u_height=1, rack_face="front", ports=24), item(hostname="mf00", rack_u=1, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "sw01", "peer_port": 1, "speed_gbps": 1}]), ] out = gen_rack.render_network("rack01", items) assert "```mermaid" in out assert "flowchart LR" in out assert "sw01
switch" in out assert "mf00" in out assert "eth0" in out assert "p1" in out assert "1G" in out def test_render_network_patch_panel_subtitle(): items = [ item(hostname="pp01", kind="patch-panel", rack_u=24, u_height=1, rack_face="front", ports=24), item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "pp01", "peer_port": 1, "speed_gbps": 1}]), ] out = gen_rack.render_network("rack01", items) assert "pp01
patch-panel" in out def test_render_network_empty_when_no_links(): items = [item(hostname="mf00", rack_u=1, u_height=1, rack_face="front")] assert gen_rack.render_network("rack01", items) == "" def test_render_network_omits_speed_when_absent(): items = [ item(hostname="sw01", kind="switch", rack_u=10, u_height=1, rack_face="front", ports=24), item(hostname="mf00", rack_u=1, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "sw01", "peer_port": 1}]), ] out = gen_rack.render_network("rack01", items) assert "eth0" in out and "p1" in out assert "·" not in out # no speed suffix rendered def test_render_network_is_deterministic(): a = item(hostname="sw01", kind="switch", rack_u=10, u_height=1, rack_face="front", ports=24) b = item(hostname="mf01", rack_u=2, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "sw01", "peer_port": 2, "speed_gbps": 1}]) c = item(hostname="mf00", rack_u=1, u_height=1, rack_face="front", links=[{"local": "eth0", "peer": "sw01", "peer_port": 1, "speed_gbps": 1}]) assert gen_rack.render_network("rack01", [a, b, c]) == \ gen_rack.render_network("rack01", [c, b, a]) def test_generate_includes_network_section(tmp_path): hw = tmp_path / "hardware" out = tmp_path / "out" hw.mkdir() _write_item( hw, "sw01", "---\nhostname: sw01\nkind: switch\nstatus: in-use\n" "rack: rack01\nrack_u: 10\nu_height: 1\nrack_face: front\nports: 24\n---\n", ) _write_item( hw, "mf00", "---\nhostname: mf00\nkind: server\nstatus: in-use\n" "rack: rack01\nrack_u: 1\nu_height: 1\nrack_face: front\n" "links:\n - { local: eth0, peer: sw01, peer_port: 1, speed_gbps: 1 }\n---\n", ) rc = gen_rack.generate(hw, out) assert rc == 0 page = (out / "rack01.md").read_text() assert "## Network" in page assert "```mermaid" in page assert "eth0" in page def test_validate_accepts_mounted_item(): gen_rack.validate_item(item(hostname="srv01", mounted_on="shf01", shelf_face="front", shelf_slot=1)) def test_validate_rejects_mounted_with_rack_u(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(hostname="srv01", mounted_on="shf01", shelf_face="front", shelf_slot=1, rack_u=5)) def test_validate_rejects_mounted_bad_shelf_face(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(hostname="srv01", mounted_on="shf01", shelf_face="left", shelf_slot=1)) def test_validate_rejects_mounted_bad_slot(): with pytest.raises(gen_rack.SchemaError): gen_rack.validate_item(item(hostname="srv01", mounted_on="shf01", shelf_face="front", shelf_slot=0)) def test_overlaps_skips_mounted_items(): items = [ item(hostname="a", mounted_on="shf01", shelf_face="front", shelf_slot=1), item(hostname="b", mounted_on="shf01", shelf_face="front", shelf_slot=2), ] gen_rack.check_overlaps(items) # no raise — mounted items claim no U-range def test_check_shelves_accepts_valid_mount(): items = [shelf(), item(hostname="srv01", mounted_on="shf01", shelf_face="front", shelf_slot=1)] gen_rack.check_shelves(items) def test_check_shelves_rejects_missing_shelf(): items = [item(hostname="srv01", mounted_on="ghost", shelf_face="front", shelf_slot=1)] with pytest.raises(gen_rack.SchemaError): gen_rack.check_shelves(items) def test_check_shelves_rejects_non_shelf_target(): items = [ item(hostname="sw01", kind="switch", rack_u=10, u_height=1, rack_face="front"), item(hostname="srv01", mounted_on="sw01", shelf_face="front", shelf_slot=1), ] with pytest.raises(gen_rack.SchemaError): gen_rack.check_shelves(items) def test_check_shelves_rejects_duplicate_slot(): items = [shelf(), item(hostname="srv01", mounted_on="shf01", shelf_face="front", shelf_slot=1), item(hostname="srv02", mounted_on="shf01", shelf_face="front", shelf_slot=1)] with pytest.raises(gen_rack.SchemaError): gen_rack.check_shelves(items) def test_generate_returns_1_on_dangling_mount(tmp_path): hw = tmp_path / "hardware" out = tmp_path / "out" hw.mkdir() _write_item( hw, "srv01", "---\nhostname: srv01\nkind: server\nstatus: in-use\n" "rack: rack01\nmounted_on: ghost\nshelf_face: front\nshelf_slot: 1\n---\n", ) rc = gen_rack.generate(hw, out) assert rc == 1 assert not (out / "rack01.md").exists() def test_render_svg_draws_shelf_and_occupants(): items = [ shelf(), item(hostname="srv01", mounted_on="shf01", shelf_face="front", shelf_slot=1), item(hostname="srv02", mounted_on="shf01", shelf_face="front", shelf_slot=2), item(hostname="srv03", mounted_on="shf01", shelf_face="rear", shelf_slot=1), ] svg = gen_rack.render_svg("rack01", items) assert "shf01" in svg assert "srv01" in svg and "srv02" in svg and "srv03" in svg # the shelf is NOT drawn as a generic full-height device box assert "shf01 (U37" not in svg def test_render_svg_shelf_is_deterministic(): base = [ shelf(), item(hostname="srv02", mounted_on="shf01", shelf_face="front", shelf_slot=2), item(hostname="srv01", mounted_on="shf01", shelf_face="front", shelf_slot=1), ] assert gen_rack.render_svg("rack01", base) == gen_rack.render_svg( "rack01", list(reversed(base)) ) def test_render_page_lists_mounted_devices(): items = [shelf(), item(hostname="srv01", mounted_on="shf01", shelf_face="front", shelf_slot=1)] page = gen_rack.render_page("rack01", items) assert "../../hardware/srv01.md" in page assert "front · shf01/1" in page assert "U37–U46" in page # mounted device shows its shelf's U-range def test_svg_boxes_link_to_host_pages(): items = [item(hostname="srv04", rack_u=5, u_height=1, rack_face="front")] svg = gen_rack.render_svg("rack01", items) assert '' in svg assert "" in svg def test_svg_status_border_styles(): staging = gen_rack.render_svg("rack01", [ item(hostname="a", rack_u=1, u_height=1, rack_face="front", status="staging")]) broken = gen_rack.render_svg("rack01", [ item(hostname="b", rack_u=1, u_height=1, rack_face="front", status="broken")]) assert 'stroke-dasharray="4 2"' in staging assert 'stroke="#e15759"' in broken and 'stroke-width="3"' in broken def test_svg_tooltip_has_cluster_and_placement(): items = [item(hostname="srv01", rack_u=1, u_height=1, rack_face="front", status="staging", cluster="tappaas")] svg = gen_rack.render_svg("rack01", items) assert "cluster: tappaas" in svg assert "U1" in svg def test_svg_has_responsive_style(): svg = gen_rack.render_svg("rack01", []) assert "max-width:100%" in svg def test_render_page_inlines_svg_with_download_link(): items = [item(hostname="srv04", rack_u=5, u_height=1, rack_face="front")] page = gen_rack.render_page("rack01", items) assert '<div class="rack-elevation">' in page assert "<svg" in page assert "[Download SVG](rack01-elevation.svg)" in page assert "![Rack rack01 elevation]" not in page def test_svg_legend_shows_present_kinds(): items = [item(hostname="sw01", kind="switch", rack_u=10, u_height=1, rack_face="front")] svg = gen_rack.render_svg("rack01", items) assert ">Legend<" in svg assert ">switch<" in svg def test_svg_legend_omits_absent_kinds(): items = [item(hostname="sw01", kind="switch", rack_u=10, u_height=1, rack_face="front")] svg = gen_rack.render_svg("rack01", items) assert ">ups<" not in svg def test_svg_u_numbers_in_both_gutters(): svg = gen_rack.render_svg("rack01", []) assert 'text-anchor="end"' in svg # left gutter assert 'text-anchor="start"' in svg # right gutter def test_svg_has_column_frames(): svg = gen_rack.render_svg("rack01", []) assert svg.count('fill="none"') >= 2 # one frame per column