Files
krow_backend/go-api/internal/owliver/context_test.go
2026-08-28 12:21:44 +05:30

287 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package owliver
import (
"testing"
"github.com/krow/krow-backend/go-api/internal/domain"
)
// Highlights — what is worth asking when nothing has been typed.
//
// Everything here is a pure function of a Context written out by hand, so the
// assertions are about the ranking itself rather than about a fixture. The
// database read that produces a real Context is exercised over HTTP, in
// internal/httpserver.
// ids is the intents a result names, in order.
func ids(list []Suggestion) []string {
out := make([]string, len(list))
for i, s := range list {
out[i] = s.Intent
}
return out
}
// has reports whether an intent was offered.
func has(list []Suggestion, intent string) bool {
for _, s := range list {
if s.Intent == intent {
return true
}
}
return false
}
// A workspace with nothing in it is asked nothing.
//
// The alternative — three questions about empty sets — is the padding this
// whole path is supposed to refuse. "You have no positions, would you like to
// know which position has the strongest pipeline?" is worse than silence.
func TestHighlightsOfAnEmptyOrganizationAreEmpty(t *testing.T) {
for _, page := range []string{"positions", "candidates", "control-center", "analytics",
"talent-pool", "hired-history", "krow-forge", "activity"} {
if got := Highlights(page, Context{}, domain.RoleAdmin); len(got) != 0 {
t.Errorf("%s offered %v for an empty organization", page, ids(got))
}
}
}
// The counts decide, and the loudest one leads.
func TestHighlightsRankByTheData(t *testing.T) {
cases := []struct {
name string
page string
ctx Context
wantTop string
}{
{
name: "unfinished drafts dominate a quiet workspace",
page: "positions",
ctx: Context{DraftPositions: 4, ActivePositions: 1},
wantTop: "position-drafts",
},
{
name: "a role nobody applied to outranks the drafts",
page: "positions",
ctx: Context{DraftPositions: 1, ActivePositions: 3, StarvedPositions: 2},
wantTop: "positions-attention",
},
{
// The tier, not the pile. This is the case weight × count gets
// wrong: forty applications outnumber one abandoned role, and the
// abandoned role is still the finding.
name: "one starved role outranks a large pile of everything else",
page: "positions",
ctx: Context{ActivePositions: 9, StarvedPositions: 1, Applications: 40, Unscreened: 22},
wantTop: "positions-attention",
},
{
// And the thing that just happened is heard, however small.
name: "a single unfinished draft is heard over a busy funnel",
page: "positions",
ctx: Context{ActivePositions: 9, DraftPositions: 1, Applications: 40, Unscreened: 22},
wantTop: "position-drafts",
},
{
name: "unscored candidates are the screening gap",
page: "candidates",
ctx: Context{Applications: 12, Unscreened: 9},
wantTop: "screening-gaps",
},
{
name: "a flagged interview is a risk before it is anything else",
page: "candidates",
ctx: Context{Applications: 12, Unscreened: 2, FlaggedRisks: 4},
wantTop: "candidate-risk",
},
{
name: "unverified profiles are the talent pool's gap",
page: "talent-pool",
ctx: Context{Profiles: 20, UnverifiedProfiles: 8},
wantTop: "talent-verification",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := Highlights(c.page, c.ctx, domain.RoleAdmin)
if len(got) == 0 {
t.Fatalf("%s offered nothing", c.page)
}
if got[0].Intent != c.wantTop {
t.Fatalf("led with %q, want %q (whole list %v)", got[0].Intent, c.wantTop, ids(got))
}
})
}
}
// A comparison needs two things to compare.
//
// "Which position has the strongest pipeline?" is not a question about a
// workspace holding one position: the answer is a table of one row, which is
// the same padding as a question about an empty set.
func TestHighlightsDoNotOfferAComparisonOfOne(t *testing.T) {
one := Highlights("positions", Context{ActivePositions: 1, Applications: 3}, domain.RoleAdmin)
if has(one, "position-strength") {
t.Fatalf("offered a pipeline comparison across one position: %v", ids(one))
}
two := Highlights("positions", Context{ActivePositions: 2, Applications: 3}, domain.RoleAdmin)
if !has(two, "position-strength") {
t.Fatalf("two positions is a comparison and was not offered: %v", ids(two))
}
}
// Never more than three, and never the same reading twice.
func TestHighlightsAreCappedAndDistinct(t *testing.T) {
loud := Context{
ActivePositions: 9, DraftPositions: 7, StarvedPositions: 5, UnderfilledActive: 6,
Applications: 40, Unscreened: 22, Shortlisted: 9, Interviewing: 6, Hired: 11,
FlaggedRisks: 4, Staff: 30, Profiles: 60, UnverifiedProfiles: 25, Courses: 14,
ActivityEvents: 900,
}
for page := range catalogue {
got := Highlights(page, loud, domain.RoleAdmin)
if len(got) > MaxSuggestions {
t.Errorf("%s returned %d, the cap is %d", page, len(got), MaxSuggestions)
}
seen := map[string]bool{}
for _, s := range got {
if seen[s.Intent] {
t.Errorf("%s repeated %q", page, s.Intent)
}
seen[s.Intent] = true
if s.Text == "" || s.Intent == "" {
t.Errorf("%s returned an incomplete suggestion: %+v", page, s)
}
// Nothing was typed, so nothing asked for a rendering.
if s.Capability != "" {
t.Errorf("%s carried a shape nobody asked for: %+v", page, s)
}
}
}
}
// Permission is decided before relevance, exactly as it is in Suggest.
//
// A count cannot promote a reading the caller may not perform: talent's rows
// are narrowed by the policy table, so an org-wide figure is not theirs to be
// told even as a ranking input.
func TestHighlightsRefuseWhatARoleCannotRead(t *testing.T) {
loud := Context{ActivePositions: 9, DraftPositions: 7, Applications: 40, Unscreened: 22,
Staff: 30, Profiles: 60}
for _, page := range []string{"positions", "candidates", "hired-history", "control-center"} {
if got := Highlights(page, loud, domain.RoleTalent); len(got) != 0 {
t.Errorf("%s offered talent %v", page, ids(got))
}
if got := Highlights(page, loud, domain.RoleAdmin); len(got) == 0 {
t.Errorf("%s offered an admin nothing — the assertion above proves nothing", page)
}
}
}
// An unrecognised role is offered nothing, as everywhere else.
func TestHighlightsDenyAnUnknownRole(t *testing.T) {
loud := Context{ActivePositions: 9, Applications: 40, Unscreened: 22}
for _, role := range []domain.Role{"", "root", "superuser", "Admin"} {
if got := Highlights("positions", loud, role); len(got) != 0 {
t.Errorf("role %q was offered %v", role, ids(got))
}
}
}
// A page the catalogue does not hold answers with an empty slice, not nil.
func TestHighlightsOfAnUnknownPageAreAnEmptySlice(t *testing.T) {
loud := Context{ActivePositions: 9, Applications: 40}
for _, page := range []string{"", "nowhere", "POSITIONS", "settings"} {
got := Highlights(page, loud, domain.RoleAdmin)
if got == nil {
t.Fatalf("page %q returned nil, which a client cannot range over", page)
}
if len(got) != 0 {
t.Errorf("page %q returned %v", page, ids(got))
}
}
}
// The same state always produces the same three in the same order.
func TestHighlightsAreDeterministic(t *testing.T) {
ctx := Context{ActivePositions: 5, DraftPositions: 3, StarvedPositions: 2,
Applications: 20, Unscreened: 7, Shortlisted: 4}
first := ids(Highlights("positions", ctx, domain.RoleAdmin))
for range 25 {
again := ids(Highlights("positions", ctx, domain.RoleAdmin))
if len(again) != len(first) {
t.Fatalf("got %v, first run was %v", again, first)
}
for i := range first {
if again[i] != first[i] {
t.Fatalf("got %v, first run was %v", again, first)
}
}
}
}
// Every declared Signal names an intent the catalogue actually holds, and is
// monotonic: more of the thing it counts can never make the reading less
// relevant. A signal that fell as its subject grew would be a sign error, and
// the symptom would be a suggestion that disappears exactly when it matters.
func TestSignalsAreMonotonic(t *testing.T) {
small := Context{
ActivePositions: 2, DraftPositions: 1, StarvedPositions: 1, UnderfilledActive: 1,
Applications: 5, Unscreened: 2, Shortlisted: 1, Interviewing: 1, Hired: 1,
FlaggedRisks: 1, Staff: 2, Profiles: 3, UnverifiedProfiles: 1, Courses: 1,
ActivityEvents: 10,
}
large := Context{
ActivePositions: 20, DraftPositions: 10, StarvedPositions: 10, UnderfilledActive: 10,
Applications: 50, Unscreened: 20, Shortlisted: 10, Interviewing: 10, Hired: 10,
FlaggedRisks: 10, Staff: 20, Profiles: 30, UnverifiedProfiles: 10, Courses: 10,
ActivityEvents: 100,
}
for page, intents := range catalogue {
for _, intent := range intents {
if intent.Signal == nil {
continue
}
lo, hi := intent.Signal(small), intent.Signal(large)
if lo < 0 || hi < 0 {
t.Errorf("%s/%s: a negative signal (%d, %d)", page, intent.ID, lo, hi)
}
if hi < lo {
t.Errorf("%s/%s: signal fell as the workspace grew (%d → %d)", page, intent.ID, lo, hi)
}
}
}
}
// A tier decides before a count does, everywhere.
//
// Stated as a property rather than as a list of pairs, because it is the whole
// design: a signal is "how much does this matter when it is happening at all",
// and a count that could climb into the tier above would make a large
// organization's biggest pile outrank its most urgent finding.
func TestATierAlwaysOutranksACount(t *testing.T) {
for tier := 1; tier <= 9; tier++ {
lowestAbove := when(1, tier+1)
highestWithin := when(maxTiebreak*10, tier) // deliberately over the cap
if highestWithin >= lowestAbove {
t.Fatalf("tier %d saturates into tier %d: %d >= %d",
tier, tier+1, highestWithin, lowestAbove)
}
}
// Nothing happening scores nothing, whatever the tier claims.
for tier := 1; tier <= 9; tier++ {
for _, count := range []int{0, -1, -1000} {
if got := when(count, tier); got != 0 {
t.Fatalf("when(%d, %d) = %d, want 0", count, tier, got)
}
}
}
}