package main import ( "net/http" "net/http/httptest" "strings" "sync/atomic" "testing" ) func TestLookupMissingDomain(t *testing.T) { s := &server{ nrenASNs: make(map[uint]struct{}), } s.ready.Store(true) req := httptest.NewRequest(http.MethodGet, "/lookup", nil) rr := httptest.NewRecorder() s.lookupHandler(rr, req) if rr.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d", rr.Code) } if !strings.Contains(rr.Body.String(), "missing domain") { t.Fatalf("expected error message in response") } } func TestLookupServiceNotReady(t *testing.T) { s := &server{ nrenASNs: make(map[uint]struct{}), } s.ready = atomic.Bool{} s.ready.Store(false) req := httptest.NewRequest(http.MethodGet, "/lookup?domain=example.com", nil) rr := httptest.NewRecorder() s.lookupHandler(rr, req) if rr.Code != http.StatusServiceUnavailable { t.Fatalf("expected 503, got %d", rr.Code) } } func TestMatchesUniDomain(t *testing.T) { set := map[string]struct{}{ "uni-stuttgart.de": {}, "hdm-stuttgart.de": {}, } tests := []struct { domain string wantMatch bool wantEntry string }{ {"uni-stuttgart.de", true, "uni-stuttgart.de"}, // exact match {"insti.uni-stuttgart.de", true, "uni-stuttgart.de"}, // single-level subdomain {"a.b.uni-stuttgart.de", true, "uni-stuttgart.de"}, // multi-level subdomain {"evil-uni-stuttgart.de", false, ""}, // lookalike non-match (different label) {"example.com", false, ""}, // not in set {"com", false, ""}, // single-label input {"uni-stuttgart.de.", true, "uni-stuttgart.de"}, // trailing dot normalised {"", false, ""}, // empty string } for _, tc := range tests { got, entry := matchesUniDomain(tc.domain, set) if got != tc.wantMatch || entry != tc.wantEntry { t.Errorf("matchesUniDomain(%q): got (%v, %q), want (%v, %q)", tc.domain, got, entry, tc.wantMatch, tc.wantEntry) } } } func TestExtractDomain(t *testing.T) { tests := []struct { input string want string }{ {"uni-stuttgart.de", "uni-stuttgart.de"}, // plain domain passthrough {"foo@uni-stuttgart.de", "uni-stuttgart.de"}, // email extraction {"FOO@UNI-STUTTGART.DE", "uni-stuttgart.de"}, // uppercase normalisation {" foo@uni-stuttgart.de ", "uni-stuttgart.de"}, // leading/trailing spaces {"notanemail", "notanemail"}, // no-@ passthrough } for _, tc := range tests { got := extractDomain(tc.input) if got != tc.want { t.Errorf("extractDomain(%q): got %q, want %q", tc.input, got, tc.want) } } } func TestLookupDomainMatch(t *testing.T) { s := &server{ nrenASNs: map[uint]struct{}{}, uniDomains: map[string]struct{}{"uni-stuttgart.de": {}}, } s.ready.Store(true) req := httptest.NewRequest(http.MethodGet, "/lookup?domain=insti.uni-stuttgart.de", nil) rr := httptest.NewRecorder() s.lookupHandler(rr, req) body := rr.Body.String() if !strings.Contains(body, `"domain_match":true`) { t.Errorf("expected domain_match:true in %s", body) } if !strings.Contains(body, `"matched_domain":"uni-stuttgart.de"`) { t.Errorf("expected matched_domain in %s", body) } if !strings.Contains(body, `"nren":true`) { t.Errorf("expected nren:true in %s", body) } if !strings.Contains(body, `"asn_match":false`) { t.Errorf("expected asn_match:false in %s", body) } } func TestLookupEmailParam(t *testing.T) { s := &server{ nrenASNs: map[uint]struct{}{}, uniDomains: map[string]struct{}{"uni-stuttgart.de": {}}, } s.ready.Store(true) req := httptest.NewRequest(http.MethodGet, "/lookup?email=student%40insti.uni-stuttgart.de", nil) rr := httptest.NewRecorder() s.lookupHandler(rr, req) body := rr.Body.String() if !strings.Contains(body, `"domain_match":true`) { t.Errorf("expected domain_match:true in %s", body) } if !strings.Contains(body, `"matched_domain":"uni-stuttgart.de"`) { t.Errorf("expected matched_domain in %s", body) } if !strings.Contains(body, `"nren":true`) { t.Errorf("expected nren:true in %s", body) } } func TestLookupEmailPrecedence(t *testing.T) { s := &server{ nrenASNs: map[uint]struct{}{}, uniDomains: map[string]struct{}{"uni-stuttgart.de": {}}, } s.ready.Store(true) // email= takes precedence over domain=; example.com is not in uniDomains req := httptest.NewRequest(http.MethodGet, "/lookup?email=a%40uni-stuttgart.de&domain=example.com", nil) rr := httptest.NewRecorder() s.lookupHandler(rr, req) body := rr.Body.String() if !strings.Contains(body, `"domain":"uni-stuttgart.de"`) { t.Errorf("expected domain uni-stuttgart.de (from email param) in %s", body) } if !strings.Contains(body, `"nren":true`) { t.Errorf("expected nren:true in %s", body) } } func TestHealthzJSON(t *testing.T) { s := &server{ asnCount: 42, minASN: 10, uniDomains: map[string]struct{}{"uni-stuttgart.de": {}, "hdm-stuttgart.de": {}}, } req := httptest.NewRequest(http.MethodGet, "/healthz", nil) rr := httptest.NewRecorder() s.healthzHandler(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, `"asn_count":42`) { t.Errorf("expected asn_count:42 in %s", body) } if !strings.Contains(body, `"domain_count":2`) { t.Errorf("expected domain_count:2 in %s", body) } if ct := rr.Header().Get("Content-Type"); !strings.Contains(ct, "application/json") { t.Errorf("expected Content-Type application/json, got %s", ct) } } func TestLookupEmailNoAt(t *testing.T) { s := &server{ nrenASNs: map[uint]struct{}{}, uniDomains: map[string]struct{}{}, } s.ready.Store(true) req := httptest.NewRequest(http.MethodGet, "/lookup?email=notanemail", nil) rr := httptest.NewRecorder() s.lookupHandler(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } if !strings.Contains(rr.Body.String(), "email param has no @") { t.Fatalf("expected error message in response, got: %s", rr.Body.String()) } }