119 lines
3.3 KiB
Go
119 lines
3.3 KiB
Go
package scan
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"testing"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func TestDetectAdapter_Greenhouse(t *testing.T) {
|
|
c := Company{
|
|
Name: "Dragos",
|
|
CareersURL: "https://job-boards.greenhouse.io/dragos",
|
|
API: "https://boards-api.greenhouse.io/v1/boards/dragos/jobs",
|
|
}
|
|
info, ok := DetectAdapter(c)
|
|
if !ok || info.Type != AdapterGreenhouse {
|
|
t.Fatalf("expected greenhouse, got %v ok=%v", info.Type, ok)
|
|
}
|
|
}
|
|
|
|
func TestDetectAdapter_Ashby(t *testing.T) {
|
|
c := Company{Name: "Acme", CareersURL: "https://jobs.ashbyhq.com/acme"}
|
|
info, ok := DetectAdapter(c)
|
|
if !ok || info.Type != AdapterAshby {
|
|
t.Fatalf("expected ashby, got %v ok=%v", info.Type, ok)
|
|
}
|
|
}
|
|
|
|
func TestDetectAdapter_Lever(t *testing.T) {
|
|
c := Company{Name: "Acme", CareersURL: "https://jobs.lever.co/acme"}
|
|
info, ok := DetectAdapter(c)
|
|
if !ok || info.Type != AdapterLever {
|
|
t.Fatalf("expected lever, got %v ok=%v", info.Type, ok)
|
|
}
|
|
}
|
|
|
|
func TestDetectAdapter_Unknown(t *testing.T) {
|
|
c := Company{Name: "Acme", CareersURL: "https://acme.com/careers"}
|
|
_, ok := DetectAdapter(c)
|
|
if ok {
|
|
t.Fatalf("expected unsupported for arbitrary careers page")
|
|
}
|
|
}
|
|
|
|
func TestTitleMatcher_PositiveOnly(t *testing.T) {
|
|
m := NewTitleMatcher(TitleFilter{Positive: []string{"Red Team", "Penetration Test"}})
|
|
if !m("Senior Red Team Operator") {
|
|
t.Fatal("expected match")
|
|
}
|
|
if m("Senior Software Engineer") {
|
|
t.Fatal("expected no match")
|
|
}
|
|
}
|
|
|
|
func TestTitleMatcher_NegativeBlocks(t *testing.T) {
|
|
m := NewTitleMatcher(TitleFilter{
|
|
Positive: []string{"Security"},
|
|
Negative: []string{"Intern", "Junior"},
|
|
})
|
|
if m("Security Intern") {
|
|
t.Fatal("negative should block")
|
|
}
|
|
if !m("Security Engineer") {
|
|
t.Fatal("positive should match")
|
|
}
|
|
}
|
|
|
|
func TestRecordJobs_BatchDedup(t *testing.T) {
|
|
db, err := sql.Open("sqlite", ":memory:")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
if _, err := db.Exec(`
|
|
CREATE TABLE jobs (url TEXT UNIQUE NOT NULL, company TEXT, title TEXT, source TEXT);
|
|
CREATE TABLE scanned_urls (url TEXT UNIQUE NOT NULL, company TEXT, title TEXT, source TEXT, status TEXT, first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
|
|
`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := &Runner{DB: db}
|
|
ctx := context.Background()
|
|
batch := []Job{
|
|
{Title: "Senior Red Team", URL: "https://example.com/jobs/1", Company: "Acme", Source: "test"},
|
|
{Title: "Staff Pentester", URL: "https://example.com/jobs/2", Company: "Acme", Source: "test"},
|
|
}
|
|
|
|
newJobs, err := r.recordJobs(ctx, batch)
|
|
if err != nil {
|
|
t.Fatalf("first batch: %v", err)
|
|
}
|
|
if len(newJobs) != 2 {
|
|
t.Fatalf("expected 2 new, got %d", len(newJobs))
|
|
}
|
|
// Re-run the same batch plus one fresh URL — only the new one should land.
|
|
batch = append(batch, Job{Title: "Principal OT", URL: "https://example.com/jobs/3", Company: "Acme", Source: "test"})
|
|
newJobs, err = r.recordJobs(ctx, batch)
|
|
if err != nil {
|
|
t.Fatalf("second batch: %v", err)
|
|
}
|
|
if len(newJobs) != 1 || newJobs[0].URL != "https://example.com/jobs/3" {
|
|
t.Fatalf("expected only jobs/3 new, got %+v", newJobs)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfig(t *testing.T) {
|
|
cfg, err := LoadConfig("adapters.yaml")
|
|
if err != nil {
|
|
t.Fatalf("LoadConfig: %v", err)
|
|
}
|
|
if len(cfg.TrackedCompanies) == 0 {
|
|
t.Fatal("expected seeded companies")
|
|
}
|
|
if len(cfg.TitleFilter.Positive) == 0 {
|
|
t.Fatal("expected positive keywords")
|
|
}
|
|
}
|