Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/integration/admin_integration_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/integration/admin_integration_test.go |
| Original SHA-256 | 91e2023c40fc40a2d69f241befdddb1b493479a4a3b1b0dbcaaf724ba6292b9f |
| Git blob SHA | 8c99c53371796ad0d5e30785b59ede6c49b8e8a0 |
| Reading-copy SHA-256 | 91e2023c40fc40a2d69f241befdddb1b493479a4a3b1b0dbcaaf724ba6292b9f |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/integration/admin_integration_test.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/integration/admin_integration_test.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓176 lines · 6.7 KB
//go:build integration
package integration
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/missionworkforce/missionmatch/internal/modules/recruiting"
"github.com/missionworkforce/missionmatch/internal/platform/security"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// WARNING. Point MM_APP_DSN and MM_MIGRATOR_DSN at a scratch or staging database, never at production.
// seedRoleUser creates a user at a given role and returns a context acting as them.
func seedRoleUser(t *testing.T, org uuid.UUID, name, role string) (uuid.UUID, context.Context) {
t.Helper()
var id uuid.UUID
if err := migrator.QueryRow(context.Background(),
`INSERT INTO users (organization_id, email, full_name, role, status)
VALUES ($1, $2, $3, $4, 'active') RETURNING id`,
org, name+"-"+uuid.New().String()[:8]+"@example.com", name, role).Scan(&id); err != nil {
t.Fatalf("seed %s: %v", role, err)
}
ctx := tenant.WithUser(tenant.WithOrg(context.Background(), org.String()),
tenant.Principal{UserID: id.String(), Role: role})
return id, ctx
}
// TestManagerCanReassignADeskAndARecruiterCannot is the behaviour the whole tier exists for, proved
// against a real database rather than against the matrix alone.
func TestManagerCanReassignADeskAndARecruiterCannot(t *testing.T) {
org := seedOrg(t)
aliceID, alice := seedRoleUser(t, org, "Alice Holder", "recruiter")
_, bob := seedRoleUser(t, org, "Bob Peer", "recruiter")
_, manager := seedRoleUser(t, org, "Mia Manager", "manager")
svc := reqSvc()
client, err := svc.CreateClient(alice, recruiting.CreateClientInput{Name: "Contested Account"})
if err != nil {
t.Fatalf("create client: %v", err)
}
id := uuid.MustParse(client.ID)
if client.OwnerName != "Alice Holder" {
t.Fatalf("the creating recruiter did not take the account, owner = %q", client.OwnerName)
}
// A peer recruiter is refused, and the refusal names who holds it.
name := "Bob's edit"
_, err = svc.UpdateClient(bob, id, recruiting.UpdateClientInput{
ExpectedUpdatedAt: client.UpdatedAt, Name: &name})
var notOwner *recruiting.ErrNotOwner
if !asErrNotOwner(err, ¬Owner) {
t.Fatalf("a peer recruiter was allowed to edit another recruiter's account: %v", err)
}
// The manager may reassign it, which before this tier required admin, and admin also carries PII
// deletion and account management.
moved, err := svc.Reassign(manager, id, aliceID)
if err != nil {
t.Fatalf("a manager could not reassign a desk: %v", err)
}
if moved.OwnerUserID != aliceID.String() {
t.Errorf("after reassignment the owner is %q", moved.OwnerName)
}
// And a recruiter still cannot.
if _, err := svc.Reassign(bob, id, aliceID); err == nil {
t.Error("a recruiter reassigned an account, so the manager tier grants nothing")
}
}
func asErrNotOwner(err error, target **recruiting.ErrNotOwner) bool {
if err == nil {
return false
}
e, ok := err.(*recruiting.ErrNotOwner)
if ok {
*target = e
}
return ok
}
// TestManagerCannotReachAdminAuthorityLive. The containment property, exercised through the service
// rather than the matrix, because a capability check that is never called protects nothing.
func TestManagerCannotReachAdminAuthorityLive(t *testing.T) {
org := seedOrg(t)
_, manager := seedRoleUser(t, org, "Mia Manager", "manager")
svc := reqSvc()
// Org settings are owner only. A manager changing the timezone would move what "today" means for
// every count in the product.
if _, err := svc.SetTimeZone(manager, "America/Chicago"); err == nil {
t.Error("a manager changed the organization timezone")
}
// But the capabilities a manager DOES hold work, or the tier is unusable.
if _, err := svc.CreateRequisition(manager, recruiting.CreateRequisitionInput{Title: "Manager role"}); err != nil {
t.Errorf("a manager could not open a job order: %v", err)
}
if _, _, err := svc.Export(manager, recruiting.ExportPipeline, recruiting.PeriodMonth, ""); err != nil {
t.Errorf("a manager could not export: %v", err)
}
}
// TestCapabilitiesTravelWithTheSession. The list the browser gates on and the checks the server enforces
// come from one matrix, so a screen cannot offer something the API will refuse.
func TestCapabilitiesMatchTheServerForEveryRole(t *testing.T) {
for _, role := range []security.Role{
security.RoleViewer, security.RoleMember, security.RoleRecruiter,
security.RoleManager, security.RoleAdmin, security.RoleOwner,
} {
caps := security.CapabilitiesOf(role)
set := map[string]bool{}
for _, c := range caps {
set[c] = true
}
for _, cap := range []security.Capability{
security.CapDeskReassign, security.CapOrgSettings, security.CapPrivacyErase,
security.CapExportRun, security.CapCandidateRead, security.CapUserInvite,
} {
if set[string(cap)] != security.Has(role, cap) {
t.Errorf("role %s: session list and server disagree about %s", role, cap)
}
}
}
}
// TestManagerRoleIsAcceptedByTheDatabase. The Go vocabulary and the CHECK constraint have to agree, or
// promoting someone is a 500.
func TestManagerRoleIsAcceptedByTheDatabase(t *testing.T) {
org := seedOrg(t)
if _, err := migrator.Exec(context.Background(),
`INSERT INTO users (organization_id, email, full_name, role, status)
VALUES ($1, $2, 'A Manager', 'manager', 'active')`,
org, "mgr-"+uuid.New().String()[:8]+"@example.com"); err != nil {
t.Fatalf("the database refused the manager role: %v", err)
}
// And a role the code does not know is still refused, so the constraint has not simply been widened
// to anything.
if _, err := migrator.Exec(context.Background(),
`INSERT INTO users (organization_id, email, full_name, role, status)
VALUES ($1, $2, 'A Wizard', 'wizard', 'active')`,
org, "wiz-"+uuid.New().String()[:8]+"@example.com"); err == nil {
t.Error("the database accepted an unknown role")
}
}
// TestReassignmentIsTenantScoped. A manager in one organization must not be able to hand another
// organization's account to anyone.
func TestReassignmentIsTenantScoped(t *testing.T) {
orgA := seedOrg(t)
_, aliceA := seedRoleUser(t, orgA, "Alice A", "recruiter")
orgB := seedOrg(t)
_, managerB := seedRoleUser(t, orgB, "Manager B", "manager")
userB, _ := seedRoleUser(t, orgB, "Recruiter B", "recruiter")
svc := reqSvc()
client, err := svc.CreateClient(aliceA, recruiting.CreateClientInput{Name: "Tenant A account"})
if err != nil {
t.Fatalf("create in A: %v", err)
}
if _, err := svc.Reassign(managerB, uuid.MustParse(client.ID), userB); err == nil {
t.Fatal("a manager reassigned an account belonging to another tenant")
}
// Control: A's own account is still hers.
got, err := svc.GetClient(aliceA, uuid.MustParse(client.ID))
if err != nil {
t.Fatalf("read back in A: %v", err)
}
if got.OwnerName != "Alice A" {
t.Errorf("owner = %q after the cross tenant attempt", got.OwnerName)
}
}