Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions internal/topology/golden_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package topology

import (
"encoding/json"
"os"
"testing"
)

func loadGolden(t *testing.T) *Snapshot {
t.Helper()
b, err := os.ReadFile("../../testdata/testenv.golden.json")
if err != nil {
t.Fatal(err)
}
var s Snapshot
if err := json.Unmarshal(b, &s); err != nil {
t.Fatalf("golden fixture does not parse: %v", err)
}
return &s
}

func TestGoldenCounts(t *testing.T) {
s := loadGolden(t)
for _, c := range []struct {
name string
got int
want int
}{
{"vpcs", len(s.VPCs), 1},
{"subnets", len(s.Subnets), 2},
{"routeTables", len(s.RouteTables), 2},
{"enis", len(s.ENIs), 5},
{"securityGroups", len(s.SecurityGroups), 4},
{"vpcEndpoints", len(s.VPCEndpoints), 1},
} {
if c.got != c.want {
t.Errorf("%s: got %d, want %d", c.name, c.got, c.want)
}
}
if s.SchemaVersion != SchemaVersion {
t.Errorf("schemaVersion = %d, want %d", s.SchemaVersion, SchemaVersion)
}
if s.Account != RedactedAccount {
t.Errorf("fixture account not sanitized: %q", s.Account)
}
}

// TestGoldenSGChain asserts the alb -> app -> db SG-references-SG chain resolves.
func TestGoldenSGChain(t *testing.T) {
s := loadGolden(t)
byName := map[string]SecurityGroup{}
for _, sg := range s.SecurityGroups {
byName[sg.Name] = sg
}
alb, app, db := byName["reachr-testenv-alb"], byName["reachr-testenv-app"], byName["reachr-testenv-db"]
if alb.ID == "" || app.ID == "" || db.ID == "" {
t.Fatalf("missing a tier SG: alb=%q app=%q db=%q", alb.ID, app.ID, db.ID)
}
if !hasIngressFromSG(app, alb.ID) {
t.Error("app SG should allow ingress from alb SG")
}
if !hasIngressFromSG(db, app.ID) {
t.Error("db SG should allow ingress from app SG")
}
}

func hasIngressFromSG(sg SecurityGroup, peerSGID string) bool {
for _, r := range sg.Rules {
if r.Direction == Ingress && r.Peer.Kind == PeerSG && r.Peer.Value == peerSGID {
return true
}
}
return false
}

// TestGoldenGatewayEndpointRoute asserts the S3 gateway-endpoint prefix-list route.
func TestGoldenGatewayEndpointRoute(t *testing.T) {
s := loadGolden(t)
for _, rt := range s.RouteTables {
for _, r := range rt.Routes {
if r.Target.Kind == TargetVPCEndpointGW && r.Destination.Kind == DestPrefixList {
return
}
}
}
t.Error("expected a prefix-list -> vpc-endpoint-gw route in the fixture")
}

// TestGoldenENIOwners asserts the service-managed ENI owners survived.
func TestGoldenENIOwners(t *testing.T) {
s := loadGolden(t)
owners := map[string]bool{}
for _, e := range s.ENIs {
if e.Attachment != nil {
owners[e.Attachment.InstanceOwnerID] = true
}
}
for _, want := range []string{"amazon-elb", "amazon-rds"} {
if !owners[want] {
t.Errorf("expected an ENI owned by %q", want)
}
}
}
48 changes: 48 additions & 0 deletions internal/topology/sanitize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package topology

import "testing"

func TestSanitizeRedactsAccount(t *testing.T) {
const acct = "123456789012"
s := &Snapshot{
Account: acct,
Subnets: []Subnet{{ARN: "arn:aws:ec2:ap-southeast-2:" + acct + ":subnet/subnet-1"}},
SecurityGroups: []SecurityGroup{{
ARN: "arn:aws:ec2:ap-southeast-2:" + acct + ":security-group/sg-1",
Rules: []SGRule{
{Peer: SGPeer{Kind: PeerSG, Value: "sg-2", Account: acct}},
{Peer: SGPeer{Kind: PeerCIDR, Value: "10.0.0.0/8"}},
},
}},
ENIs: []ENI{
{Attachment: &ENIAttachment{InstanceOwnerID: acct}}, // customer instance
{Attachment: &ENIAttachment{InstanceOwnerID: "amazon-rds"}}, // service-managed
},
}

Sanitize(s)

if s.Account != RedactedAccount {
t.Errorf("Account = %q, want %q", s.Account, RedactedAccount)
}
if got := s.Subnets[0].ARN; got != "arn:aws:ec2:ap-southeast-2:"+RedactedAccount+":subnet/subnet-1" {
t.Errorf("subnet ARN not redacted: %q", got)
}
if got := s.SecurityGroups[0].Rules[0].Peer.Account; got != RedactedAccount {
t.Errorf("SG-peer account = %q, want redacted", got)
}
if got := s.ENIs[0].Attachment.InstanceOwnerID; got != RedactedAccount {
t.Errorf("instance owner = %q, want redacted", got)
}
if got := s.ENIs[1].Attachment.InstanceOwnerID; got != "amazon-rds" {
t.Errorf("service owner should be preserved, got %q", got)
}
}

func TestSanitizeIsIdempotent(t *testing.T) {
s := &Snapshot{Account: RedactedAccount}
Sanitize(s) // must not panic or alter an already-redacted snapshot
if s.Account != RedactedAccount {
t.Errorf("Account = %q", s.Account)
}
}
51 changes: 51 additions & 0 deletions internal/topology/snapshot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package topology

import (
"encoding/json"
"reflect"
"testing"
"time"
)

func p(v int32) *int32 { return &v }

// TestRoundTrip guards schema stability and the pointer-port nil semantics
// (nil FromPort/ToPort = "all ports", which must survive marshal/unmarshal).
func TestRoundTrip(t *testing.T) {
want := &Snapshot{
SchemaVersion: SchemaVersion,
ScannedAt: time.Date(2026, 7, 12, 9, 58, 23, 0, time.UTC),
Region: "ap-southeast-2",
Account: RedactedAccount,
RouteTables: []RouteTable{{
ID: "rtb-1", VPCID: "vpc-1", Main: true,
Routes: []Route{{
Destination: RouteDestination{Kind: DestPrefixList, Value: "pl-1"},
Target: RouteTarget{Kind: TargetVPCEndpointGW, ID: "vpce-1"},
State: "active",
}},
}},
SecurityGroups: []SecurityGroup{{
ID: "sg-1", VPCID: "vpc-1", Name: "app",
Rules: []SGRule{
{Direction: Ingress, Protocol: "tcp", FromPort: p(8080), ToPort: p(8080), Peer: SGPeer{Kind: PeerSG, Value: "sg-2"}},
{Direction: Ingress, Protocol: "all", Peer: SGPeer{Kind: PeerSG, Value: "sg-1"}}, // nil ports = all
},
}},
}

b, err := json.Marshal(want)
if err != nil {
t.Fatal(err)
}
var got Snapshot
if err := json.Unmarshal(b, &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, &got) {
t.Errorf("round-trip mismatch:\n want %+v\n got %+v", want, &got)
}
if got.SecurityGroups[0].Rules[1].FromPort != nil {
t.Error("nil FromPort (all-ports) did not survive round-trip")
}
}