package main import ( "fmt" "net/http" "net/http/httptest" "strings" "testing" ) func TestCheckPasswdConstraint(t *testing.T) { tests := []struct { name string pass string wantErr bool }{ {"valid password", "Correct1Horse", false}, {"too short", "Short1A", true}, {"exactly 12 chars", "Abcdefgh1234", false}, {"no uppercase", "correct1horse", true}, {"no lowercase", "CORRECT1HORSE", true}, {"no digit", "CorrectHorse!", true}, {"exactly 128 chars", strings.Repeat("a", 126) + "A1", false}, {"129 chars is too long", strings.Repeat("a", 127) + "A1", true}, {"very long password", strings.Repeat("Aa1", 100), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := checkPasswdConstraint(tt.pass) if (err != nil) != tt.wantErr { t.Errorf("checkPasswdConstraint(%q) error = %v, wantErr %v", tt.pass, err, tt.wantErr) } }) } } // TestChangePasswordAPI exercises the request-validation paths of the JSON // password change API that return before any LDAP connection is attempted. func TestChangePasswordAPI(t *testing.T) { const secret = "s3cr3t-token" // Restore the global secret after the test. orig := changeAPISecret t.Cleanup(func() { changeAPISecret = orig }) tests := []struct { name string secret string // value of changeAPISecret for this case authHeader string // Authorization header ("" means none) body string wantStatus int }{ { name: "API disabled", secret: "", authHeader: "Bearer " + secret, body: `{"username":"alice","old_password":"OldPass12345","new_password":"NewPass12345"}`, wantStatus: http.StatusServiceUnavailable, }, { name: "missing token", secret: secret, authHeader: "", body: `{"username":"alice","old_password":"OldPass12345","new_password":"NewPass12345"}`, wantStatus: http.StatusUnauthorized, }, { name: "wrong token", secret: secret, authHeader: "Bearer nope", body: `{"username":"alice","old_password":"OldPass12345","new_password":"NewPass12345"}`, wantStatus: http.StatusUnauthorized, }, { name: "malformed authorization header", secret: secret, authHeader: secret, body: `{"username":"alice","old_password":"OldPass12345","new_password":"NewPass12345"}`, wantStatus: http.StatusUnauthorized, }, { name: "invalid JSON", secret: secret, authHeader: "Bearer " + secret, body: `{not json`, wantStatus: http.StatusBadRequest, }, { name: "missing username", secret: secret, authHeader: "Bearer " + secret, body: `{"old_password":"OldPass12345","new_password":"NewPass12345"}`, wantStatus: http.StatusBadRequest, }, { name: "missing passwords", secret: secret, authHeader: "Bearer " + secret, body: `{"username":"alice"}`, wantStatus: http.StatusBadRequest, }, { name: "weak new password", secret: secret, authHeader: "Bearer " + secret, body: `{"username":"alice","old_password":"OldPass12345","new_password":"short"}`, wantStatus: http.StatusNotAcceptable, }, } for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { changeAPISecret = tt.secret req := httptest.NewRequest(http.MethodPost, "/api/v1/password", strings.NewReader(tt.body)) if tt.authHeader != "" { req.Header.Set("Authorization", tt.authHeader) } // Unique remote address per case so the shared rate limiter never trips. req.RemoteAddr = fmt.Sprintf("192.0.2.%d:1234", i+1) w := httptest.NewRecorder() changePasswordAPI(w, req) if w.Code != tt.wantStatus { t.Errorf("status = %d, want %d (body: %s)", w.Code, tt.wantStatus, w.Body.String()) } }) } } func TestCheckChangeAPIAuthorization(t *testing.T) { orig := changeAPISecret t.Cleanup(func() { changeAPISecret = orig }) changeAPISecret = "s3cr3t-token" tests := []struct { name string header string want bool }{ {"valid", "Bearer s3cr3t-token", true}, {"case-insensitive scheme", "bearer s3cr3t-token", true}, {"wrong token", "Bearer wrong", false}, {"missing", "", false}, {"no scheme", "s3cr3t-token", false}, {"wrong scheme", "Basic s3cr3t-token", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/api/v1/password", nil) if tt.header != "" { req.Header.Set("Authorization", tt.header) } if got := checkChangeAPIAuthorization(req); got != tt.want { t.Errorf("checkChangeAPIAuthorization(%q) = %v, want %v", tt.header, got, tt.want) } }) } }