package app_test import ( "context" "crypto/tls" "io" "net/http" "strings" "testing" "time" "github.com/owen/vibedns/internal/app" ) // TestBlockPageServesMatchedPolicy exercises the whole path an operator sets // up: a blacklist, a policy that sinkholes to this host, and a network that // covers the test client. Hitting the block page listener for the blocked // hostname should re-evaluate the same policy the DNS engine would have used, // and the rendered page should carry the matched list and policy names. func TestBlockPageServesMatchedPolicy(t *testing.T) { a := newTestApp(t) ctx := context.Background() list, err := a.CreateDomainList(ctx, testActor(), app.ListInput{ Kind: "blacklist", Name: "Test Ads", }) if err != nil { t.Fatalf("create list: %v", err) } if _, err := a.AddDomain(ctx, testActor(), list.ID, "ads.example.com", true, ""); err != nil { t.Fatalf("add domain: %v", err) } policy, err := a.CreatePolicy(ctx, testActor(), app.PolicyInput{ Name: "Block Test", BlockAction: "sinkhole", SinkholeIPv4: "127.0.0.1", SinkholeIPv6: "::1", ListIDs: []int64{list.ID}, }) if err != nil { t.Fatalf("create policy: %v", err) } if _, err := a.CreateNetwork(ctx, testActor(), app.NetworkInput{ Name: "Test Client", CIDR: "127.0.0.1/32", PolicyIDs: []int64{policy.ID}, }); err != nil { t.Fatalf("create network: %v", err) } if err := a.Reload(ctx); err != nil { t.Fatalf("reload: %v", err) } const httpAddr = "127.0.0.1:18280" const httpsAddr = "127.0.0.1:18243" if err := a.BlockPage.Start(httpAddr, httpsAddr); err != nil { t.Fatalf("start block page server: %v", err) } t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = a.BlockPage.Shutdown(ctx) }) client := &http.Client{ Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, Timeout: 5 * time.Second, } // Wait for the listener to actually accept, rather than sleeping blindly. deadline := time.Now().Add(2 * time.Second) for { if a.BlockPage.Running() { break } if time.Now().After(deadline) { t.Fatal("block page server never reported running") } time.Sleep(10 * time.Millisecond) } req, err := http.NewRequest(http.MethodGet, "http://"+httpAddr+"/tracker.js", nil) if err != nil { t.Fatalf("build request: %v", err) } req.Host = "ads.example.com" resp, err := client.Do(req) if err != nil { t.Fatalf("GET block page: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("read body: %v", err) } if resp.StatusCode != http.StatusOK { t.Fatalf("status = %d, want 200", resp.StatusCode) } page := string(body) if !strings.Contains(page, "Test Ads") { t.Errorf("page does not mention the matched list name: %s", page) } if !strings.Contains(page, "Block Test") { t.Errorf("page does not mention the matched policy name: %s", page) } if !strings.Contains(page, "ads.example.com") { t.Errorf("page does not mention the requested domain: %s", page) } httpsResp, err := client.Get("https://" + httpsAddr + "/") if err != nil { t.Fatalf("GET https block page: %v", err) } defer httpsResp.Body.Close() if httpsResp.StatusCode != http.StatusOK { t.Fatalf("https status = %d, want 200", httpsResp.StatusCode) } }