213 lines
5.7 KiB
Go
213 lines
5.7 KiB
Go
package data
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/santifer/career-ops/dashboard/internal/model"
|
|
)
|
|
|
|
func TestDeriveNoteFields(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
app model.CareerApplication
|
|
location string
|
|
workMode string
|
|
payRange string
|
|
paySrc string
|
|
last string
|
|
}{
|
|
{
|
|
name: "remote with posted comma range and rejection date",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-04",
|
|
Notes: "Remote US (EST/CST). Base $174,986-209,983 + RSUs (POSTED). Rejected 2026-06-05 (not moving forward). Via Greenhouse",
|
|
},
|
|
workMode: "Remote",
|
|
payRange: "$174,986-209,983",
|
|
paySrc: "POSTED",
|
|
last: "2026-06-05",
|
|
},
|
|
{
|
|
name: "hybrid city state with estimate",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-03",
|
|
Notes: "Charlotte NC (Hybrid), via LinkedIn. Comp ~$130-170K (est). Application VIEWED by recruiter 2026-06-04",
|
|
},
|
|
location: "Charlotte, NC",
|
|
workMode: "Hybrid",
|
|
payRange: "~$130-170K",
|
|
paySrc: "est",
|
|
last: "2026-06-04",
|
|
},
|
|
{
|
|
name: "bare location implies full onsite, decimal K range",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-01",
|
|
Notes: "Austin TX (location mismatch). Salary $124.2-198.7K (POSTED)",
|
|
},
|
|
location: "Austin, TX",
|
|
workMode: "Full",
|
|
payRange: "$124.2-198.7K",
|
|
paySrc: "POSTED",
|
|
last: "2026-06-01",
|
|
},
|
|
{
|
|
name: "lone amount fallback when no range, date falls back to applied",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-02",
|
|
Notes: "Via LinkedIn (recruiting agency). Sam stated $170K min floor",
|
|
},
|
|
workMode: "",
|
|
payRange: "$170K",
|
|
last: "2026-06-02",
|
|
},
|
|
{
|
|
name: "range preferred over earlier lone amount",
|
|
app: model.CareerApplication{
|
|
Date: "2026-05-31",
|
|
Notes: "Comp $100-175K base + 10% bonus + $300 health credit (recruiter-confirmed). Phone screen DONE 2026-06-03",
|
|
},
|
|
payRange: "$100-175K",
|
|
last: "2026-06-03",
|
|
},
|
|
{
|
|
name: "city falls back to role title, timezone parens are not an estimate",
|
|
app: model.CareerApplication{
|
|
Date: "2026-05-31",
|
|
Role: "Sr Software Engineer, Enterprise Systems — Charlotte, NC",
|
|
Notes: "Referral via friend. Remote US (EST/CST). Comp $100-175K base (recruiter-confirmed)",
|
|
},
|
|
location: "Charlotte, NC",
|
|
workMode: "Remote",
|
|
payRange: "$100-175K",
|
|
paySrc: "",
|
|
last: "2026-05-31",
|
|
},
|
|
{
|
|
name: "marketing role and interest prose are not estimate markers",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-01",
|
|
Role: "Product Marketing Manager",
|
|
Notes: "Strong fit (AI-augmented interest). Salary $140-180K. Via Lever",
|
|
},
|
|
payRange: "$140-180K",
|
|
paySrc: "",
|
|
last: "2026-06-01",
|
|
},
|
|
{
|
|
name: "no false-positive city from prose",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-01",
|
|
Notes: "Strong fit for Sams AI-augmented edge. Rejected by recruiter Nadia Kong",
|
|
},
|
|
location: "",
|
|
workMode: "",
|
|
last: "2026-06-01",
|
|
},
|
|
{
|
|
name: "remote EU with EUR estimate range",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-10",
|
|
Notes: "Remote EU (Portugal eligible). Comp ~€130-170K (est). Applied 2026-06-10",
|
|
},
|
|
location: "",
|
|
workMode: "Remote",
|
|
payRange: "~€130-170K",
|
|
paySrc: "est",
|
|
last: "2026-06-10",
|
|
},
|
|
{
|
|
name: "international city hybrid with EUR posted",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-09",
|
|
Notes: "Berlin (Hybrid). Base €90-110K (POSTED). Via Greenhouse",
|
|
},
|
|
location: "Berlin",
|
|
workMode: "Hybrid",
|
|
payRange: "€90-110K",
|
|
paySrc: "POSTED",
|
|
last: "2026-06-09",
|
|
},
|
|
{
|
|
name: "CHF range, bare intl city implies onsite",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-08",
|
|
Notes: "Zurich. CHF 165-185K. Check Point-backed",
|
|
},
|
|
location: "Zurich",
|
|
workMode: "Full",
|
|
payRange: "CHF 165-185K",
|
|
paySrc: "",
|
|
last: "2026-06-08",
|
|
},
|
|
{
|
|
name: "GBP posted range, London",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-07",
|
|
Notes: "London. £175-225K (POSTED). 30+ teams",
|
|
},
|
|
location: "London",
|
|
workMode: "Full",
|
|
payRange: "£175-225K",
|
|
paySrc: "POSTED",
|
|
last: "2026-06-07",
|
|
},
|
|
{
|
|
name: "Portugal home market, city from role title",
|
|
app: model.CareerApplication{
|
|
Date: "2026-06-06",
|
|
Role: "Senior Engineering Manager — Porto",
|
|
Notes: "Onsite. Comp €60-90K (est, below floor)",
|
|
},
|
|
location: "Porto",
|
|
workMode: "Full",
|
|
payRange: "€60-90K",
|
|
paySrc: "est",
|
|
last: "2026-06-06",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
deriveNoteFields(&tc.app)
|
|
if want := payCeiling(tc.payRange); tc.app.PayMax != want {
|
|
t.Errorf("PayMax = %v, want %v", tc.app.PayMax, want)
|
|
}
|
|
if tc.app.Location != tc.location {
|
|
t.Errorf("Location = %q, want %q", tc.app.Location, tc.location)
|
|
}
|
|
if tc.app.WorkMode != tc.workMode {
|
|
t.Errorf("WorkMode = %q, want %q", tc.app.WorkMode, tc.workMode)
|
|
}
|
|
if tc.app.PayRange != tc.payRange {
|
|
t.Errorf("PayRange = %q, want %q", tc.app.PayRange, tc.payRange)
|
|
}
|
|
if tc.app.PaySource != tc.paySrc {
|
|
t.Errorf("PaySource = %q, want %q", tc.app.PaySource, tc.paySrc)
|
|
}
|
|
if tc.app.LastContact != tc.last {
|
|
t.Errorf("LastContact = %q, want %q", tc.app.LastContact, tc.last)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPayCeiling(t *testing.T) {
|
|
cases := map[string]float64{
|
|
"$140-210K": 210_000,
|
|
"$174,986-209,983": 209_983,
|
|
"~$124.2-198.7K": 198_700,
|
|
"$170K": 170_000,
|
|
"$95-159K": 159_000,
|
|
"€130-170K": 170_000,
|
|
"£175-225K": 225_000,
|
|
"CHF 165-185K": 185_000,
|
|
"": 0,
|
|
}
|
|
for span, want := range cases {
|
|
if got := payCeiling(span); got != want {
|
|
t.Errorf("payCeiling(%q) = %v, want %v", span, got, want)
|
|
}
|
|
}
|
|
}
|