/* ·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· : : : █▀ █ █▀▀ · Blazing-fast pentesting suite : : ▄█ █ █▀ · BSD 3-Clause License : : : : (c) 2022-2026 vmfunc, xyzeva, : : lunchcat alumni & contributors : : : ·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· */ package frameworks_test import ( "net/http" "net/http/httptest" "testing" "time" "github.com/dropalldatabases/sif/internal/scan/frameworks" // Import detectors to register them via init() _ "github.com/dropalldatabases/sif/internal/scan/frameworks/detectors" ) func TestExtractVersion_Laravel(t *testing.T) { tests := []struct { body string expected string }{ {"Laravel 8.0.0", "8.0.0"}, {"Laravel v9.52.1", "9.52.1"}, {"Laravel 10.0", "10.0"}, {"no version here", "unknown"}, } for _, tt := range tests { result := frameworks.ExtractVersionOptimized(tt.body, "Laravel").Version if result != tt.expected { t.Errorf("ExtractVersionOptimized(%q, 'Laravel') = %q, want %q", tt.body, result, tt.expected) } } } func TestExtractVersion_Django(t *testing.T) { tests := []struct { body string expected string }{ {"Django 4.2.0", "4.2.0"}, {"Django/3.2.1", "3.2.1"}, {"no version", "unknown"}, } for _, tt := range tests { result := frameworks.ExtractVersionOptimized(tt.body, "Django").Version if result != tt.expected { t.Errorf("ExtractVersionOptimized(%q, 'Django') = %q, want %q", tt.body, result, tt.expected) } } } func TestExtractVersion_NextJS(t *testing.T) { tests := []struct { body string expected string }{ {"Next.js 13.4.0", "13.4.0"}, {"Next.js/14.0.1", "14.0.1"}, {"no version", "unknown"}, } for _, tt := range tests { result := frameworks.ExtractVersionOptimized(tt.body, "Next.js").Version if result != tt.expected { t.Errorf("ExtractVersionOptimized(%q, 'Next.js') = %q, want %q", tt.body, result, tt.expected) } } } func TestDetectFramework_NextJS(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") w.WriteHeader(http.StatusOK) w.Write([]byte(` Test `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Next.js" { t.Errorf("expected framework 'Next.js', got '%s'", result.Name) } if result.Confidence <= 0 { t.Error("expected positive confidence") } } func TestDetectFramework_Express(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Powered-By", "Express") w.WriteHeader(http.StatusOK) w.Write([]byte(`Hello`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Express.js" { t.Errorf("expected framework 'Express.js', got '%s'", result.Name) } } func TestDetectFramework_WordPress(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "WordPress" { t.Errorf("expected framework 'WordPress', got '%s'", result.Name) } } func TestDetectFramework_ASPNET(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-AspNet-Version", "4.0.30319") w.WriteHeader(http.StatusOK) w.Write([]byte(` `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "ASP.NET" { t.Errorf("expected framework 'ASP.NET', got '%s'", result.Name) } } func TestDetectFramework_ASPNETPoweredByHeader(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-AspNetMvc-Version", "5.2") w.Header().Set("X-Powered-By", "ASP.NET") w.WriteHeader(http.StatusOK) w.Write([]byte(`home`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "ASP.NET" { t.Errorf("expected framework 'ASP.NET', got '%s'", result.Name) } } func TestDetectFramework_NoMatch(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`Simple page`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } // result can be nil or have low confidence for unrecognized frameworks if result != nil && result.Confidence > 0.6 { t.Errorf("expected low confidence or nil result for plain HTML, got %s with %.2f", result.Name, result.Confidence) } } func TestFrameworkResult_Fields(t *testing.T) { result := frameworks.NewFrameworkResult("Laravel", "9.0.0", 0.85, 0.9) result.WithVulnerabilities([]string{"CVE-2021-3129"}, []string{"Update to latest version"}) if result.Name != "Laravel" { t.Errorf("expected Name 'Laravel', got '%s'", result.Name) } if result.Version != "9.0.0" { t.Errorf("expected Version '9.0.0', got '%s'", result.Version) } if result.Confidence != 0.85 { t.Errorf("expected Confidence 0.85, got %f", result.Confidence) } if result.VersionConfidence != 0.9 { t.Errorf("expected VersionConfidence 0.9, got %f", result.VersionConfidence) } if len(result.CVEs) != 1 { t.Errorf("expected 1 CVE, got %d", len(result.CVEs)) } if len(result.Suggestions) != 1 { t.Errorf("expected 1 suggestion, got %d", len(result.Suggestions)) } } func TestExtractVersionWithConfidence(t *testing.T) { tests := []struct { name string body string framework string wantVer string minConf float32 }{ {"Laravel explicit", "Laravel 8.0.0", "Laravel", "8.0.0", 0.8}, {"Angular ng-version", ``, "Angular", "14.2.0", 0.9}, {"WordPress generator", ``, "WordPress", "6.1.0", 0.9}, {"Vue CDN", "vue@3.2.0/dist", "Vue.js", "3.2.0", 0.7}, {"No version", "Hello World", "Laravel", "unknown", 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := frameworks.ExtractVersionOptimized(tt.body, tt.framework) if result.Version != tt.wantVer { t.Errorf("ExtractVersionOptimized() version = %q, want %q", result.Version, tt.wantVer) } if result.Confidence < tt.minConf { t.Errorf("ExtractVersionOptimized() confidence = %f, want >= %f", result.Confidence, tt.minConf) } }) } } func TestDetermineRiskLevel(t *testing.T) { tests := []struct { name string cves []string expected string }{ {"no CVEs", []string{}, "low"}, {"critical", []string{"CVE-2021-3129 (critical)"}, "critical"}, {"high", []string{"CVE-2023-22795 (high)"}, "high"}, {"medium", []string{"CVE-2023-46298 (medium)"}, "medium"}, {"mixed - critical wins", []string{"CVE-2023-1 (medium)", "CVE-2021-3129 (critical)"}, "critical"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test via WithVulnerabilities which uses determineRiskLevel internally result := frameworks.NewFrameworkResult("Test", "1.0", 0.5, 0.5) result.WithVulnerabilities(tt.cves, nil) if result.RiskLevel != tt.expected { t.Errorf("determineRiskLevel() = %q, want %q", result.RiskLevel, tt.expected) } }) } } func TestDetectFramework_Vue(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` Vue App
Loading...
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Vue.js" { t.Errorf("expected framework 'Vue.js', got '%s'", result.Name) } } func TestDetectFramework_Angular(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` Angular App `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Angular" { t.Errorf("expected framework 'Angular', got '%s'", result.Name) } } func TestDetectFramework_React(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` React App
Content
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "React" { t.Errorf("expected framework 'React', got '%s'", result.Name) } } func TestDetectFramework_Svelte(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` Svelte App
Content
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Svelte" { t.Errorf("expected framework 'Svelte', got '%s'", result.Name) } } func TestDetectFramework_Joomla(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`
Content
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Joomla" { t.Errorf("expected framework 'Joomla', got '%s'", result.Name) } } func TestDetectFramework_AdonisJS(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Set-Cookie", "adonis-session=s%3Aabc.def; Path=/; HttpOnly") w.WriteHeader(http.StatusOK) w.Write([]byte(`Welcome`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "AdonisJS" { t.Errorf("expected framework 'AdonisJS', got '%s'", result.Name) } } func TestDetectFramework_AdonisFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` Adonis Cosmetics

Adonis Cosmetics

Shop the adonis collection `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "AdonisJS" { t.Errorf("false positive: plain page mentioning 'Adonis' detected as AdonisJS (%.2f)", result.Confidence) } } func TestDetectFramework_Phoenix(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` Phoenix App
Content
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Phoenix" { t.Errorf("expected framework 'Phoenix', got '%s'", result.Name) } } func TestDetectFramework_PhoenixFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` Phoenix AZ Roofing
Serving Phoenix, Arizona since 1998.
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Phoenix" { t.Errorf("false positive: phx- CSS class page detected as Phoenix (%.2f)", result.Confidence) } } func TestDetectFramework_Astro(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`
Content
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Astro" { t.Errorf("expected framework 'Astro', got '%s'", result.Name) } } func TestDetectFramework_Ghost(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` Content `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Ghost" { t.Errorf("expected framework 'Ghost', got '%s'", result.Name) } } func TestDetectFramework_GhostButtonNoMatch(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` Sign up `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Ghost" { t.Errorf("expected no Ghost detection for a ghost-button page, got confidence %.2f", result.Confidence) } } func TestDetectFramework_GhostAPIPath(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Ghost" { t.Errorf("expected framework 'Ghost', got '%s'", result.Name) } } func TestExtractVersion_Astro(t *testing.T) { tests := []struct { body string expected string }{ {``, "4.2.0"}, {``, "3.5.1"}, {"Astro 4.0.0", "4.0.0"}, {"Astro/3.2.1", "3.2.1"}, {`"astro": "^4.1.0"`, "4.1.0"}, {`"astro": "~3.0.5"`, "3.0.5"}, {"no version", "unknown"}, } for _, tt := range tests { result := frameworks.ExtractVersionOptimized(tt.body, "Astro").Version if result != tt.expected { t.Errorf("ExtractVersionOptimized(%q, 'Astro') = %q, want %q", tt.body, result, tt.expected) } } } func TestCVEEntry_Fields(t *testing.T) { entry := frameworks.CVEEntry{ CVE: "CVE-2021-3129", AffectedVersions: []string{"8.0.0", "8.0.1"}, FixedVersion: "8.4.2", Severity: "critical", Description: "RCE vulnerability", Recommendations: []string{"Update immediately"}, } if entry.CVE != "CVE-2021-3129" { t.Errorf("expected CVE 'CVE-2021-3129', got '%s'", entry.CVE) } if len(entry.AffectedVersions) != 2 { t.Errorf("expected 2 affected versions, got %d", len(entry.AffectedVersions)) } if entry.Severity != "critical" { t.Errorf("expected Severity 'critical', got '%s'", entry.Severity) } } func TestDetectorRegistry(t *testing.T) { detectors := frameworks.GetDetectors() if len(detectors) == 0 { t.Fatal("expected registered detectors, got none") } // Check that some expected detectors are registered expectedDetectors := []string{"Laravel", "Django", "React", "Vue.js", "Angular", "Next.js", "WordPress", "Astro"} for _, name := range expectedDetectors { if _, ok := frameworks.GetDetector(name); !ok { t.Errorf("expected detector %q to be registered", name) } } } func TestExtractVersion_Htmx(t *testing.T) { tests := []struct { body string expected string }{ {``, "1.9.10"}, {`https://cdn.jsdelivr.net/npm/htmx@2.0.3/dist/htmx.min.js`, "2.0.3"}, {`"htmx.org": "^1.9.12"`, "1.9.12"}, {"no version", "unknown"}, } for _, tt := range tests { result := frameworks.ExtractVersionOptimized(tt.body, "htmx").Version if result != tt.expected { t.Errorf("ExtractVersionOptimized(%q, 'htmx') = %q, want %q", tt.body, result, tt.expected) } } } func TestDetectFramework_Htmx(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") w.WriteHeader(http.StatusOK) w.Write([]byte(`
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "htmx" { t.Errorf("expected framework 'htmx', got '%s'", result.Name) } if result.Version != "1.9.10" { t.Errorf("expected version '1.9.10', got '%s'", result.Version) } } func TestDetectFramework_GinFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.WriteHeader(http.StatusOK) w.Write([]byte(`Welcome`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Gin" { t.Errorf("false positive: detected Gin (confidence %.2f) on a CORS header", result.Confidence) } } func TestDetectFramework_Gin(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) w.Write([]byte(`404 page not found - powered by gin-gonic`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil || result.Name != "Gin" { t.Errorf("expected framework 'Gin', got '%v'", result) } } func TestDetectFramework_MeteorFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`

a meteor shower lit the sky while meteorology students tracked the meteorite.

`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Meteor" { t.Errorf("false positive: detected Meteor (confidence %.2f) on prose about meteors", result.Confidence) } } func TestDetectFramework_Meteor(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil || result.Name != "Meteor" { t.Errorf("expected framework 'Meteor', got '%v'", result) } } func TestDetectFramework_BackboneFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`

our team is the backbone of the company, the backbone network that keeps everything running.

`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Backbone.js" { t.Errorf("false positive: detected Backbone.js (confidence %.2f) on prose about backbones", result.Confidence) } } func TestDetectFramework_Backbone(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` `)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil || result.Name != "Backbone.js" { t.Errorf("expected framework 'Backbone.js', got '%v'", result) } } func TestDetectFramework_CakePHPFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`

our cupcake and cheesecake recipes, plus the best pancake stack in town.

`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "CakePHP" { t.Errorf("false positive: detected CakePHP (confidence %.2f) on prose about cakes", result.Confidence) } } func TestDetectFramework_CakePHP(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Set-Cookie", "CAKEPHP=abc123; path=/") w.WriteHeader(http.StatusOK) w.Write([]byte(`Home`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil || result.Name != "CakePHP" { t.Errorf("expected framework 'CakePHP', got '%v'", result) } } func TestDetectFramework_SvelteFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`

the model cut a svelte figure on the runway.

`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Svelte" { t.Errorf("false positive: detected Svelte (confidence %.2f) on prose with 'svelte'", result.Confidence) } } func TestDetectFramework_StrapiFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(``)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Strapi" { t.Errorf("false positive: detected Strapi (confidence %.2f) on a plain /api/ path", result.Confidence) } } func TestDetectFramework_Strapi(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`
powered by strapi
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil || result.Name != "Strapi" { t.Errorf("expected framework 'Strapi', got '%v'", result) } } func TestDetectFramework_Ember(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`Ember App
Content
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil || result.Name != "Ember.js" { t.Errorf("expected framework 'Ember.js', got '%v'", result) } } func TestDetectFramework_EmberFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`Day of the Dead

a celebratory holiday to remember the dead; families remember departed members every November and September.

`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Ember.js" { t.Errorf("false positive: detected Ember.js (confidence %.2f) on prose with 'remember'", result.Confidence) } } func TestDetectFramework_Shopify(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Powered-By", "Shopify") w.WriteHeader(http.StatusOK) w.Write([]byte(`
Store
`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.Name != "Shopify" { t.Errorf("expected framework 'Shopify', got '%s'", result.Name) } } func TestDetectFramework_ShopifyFalsePositive(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(` 10 Best Shopify Alternatives in 2026

Is Shopify Right For You?

We compare Shopify with other e-commerce platforms.

`)) })) defer server.Close() result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != nil && result.Name == "Shopify" { t.Errorf("false positive: article mentioning Shopify detected as Shopify (%.2f)", result.Confidence) } }