From bccd7f8f354dec05152d1d1f7886b3a04fb5b0c4 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Tue, 22 Sep 2026 13:36:08 +0200 Subject: [PATCH] test(07-01): add failing tests for passwords, locale, and validation Co-authored-by: Cursor --- bouncer/password_test.go | 46 +++++++++++++++++++++++++ lagoon/validate_test.go | 55 ++++++++++++++++++++++++++++++ surf/locale_from_principal_test.go | 53 ++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 bouncer/password_test.go create mode 100644 surf/locale_from_principal_test.go diff --git a/bouncer/password_test.go b/bouncer/password_test.go new file mode 100644 index 0000000..59a7561 --- /dev/null +++ b/bouncer/password_test.go @@ -0,0 +1,46 @@ +package bouncer + +import "testing" + +func TestPasswordHashAndCheck(t *testing.T) { + hash, err := HashPassword(10, "secret") + if err != nil { + t.Fatal(err) + } + if !CheckPassword(hash, "secret") { + t.Fatal("matching password rejected") + } + if CheckPassword(hash, "wrong") { + t.Fatal("wrong password accepted") + } + if CheckPassword("not-a-hash", "secret") { + t.Fatal("malformed hash must not panic or match") + } +} + +func TestPasswordAcceptsPHPHash(t *testing.T) { + // php -r 'echo password_hash("golem15-a1-check", PASSWORD_BCRYPT, ["cost"=>10]);' + const phpHash = "$2y$10$vvjEAuqFJXs6lWVy1eo5FuTZZr84LrP8Oz2c6pzAw4f2pk6u2xV5W" + if !CheckPassword(phpHash, "golem15-a1-check") { + t.Fatal("PHP $2y$ hash rejected") + } + if CheckPassword(phpHash, "other") { + t.Fatal("PHP hash matched the wrong password") + } +} + +func TestNeedsRehash(t *testing.T) { + hash, err := HashPassword(10, "secret") + if err != nil { + t.Fatal(err) + } + if !NeedsRehash(hash, 12) { + t.Fatal("lower cost must need rehash") + } + if NeedsRehash(hash, 10) || NeedsRehash(hash, 8) { + t.Fatal("equal or higher cost must not need rehash") + } + if !NeedsRehash("not-a-hash", 10) { + t.Fatal("unparseable hash must need rehash") + } +} diff --git a/lagoon/validate_test.go b/lagoon/validate_test.go index e72ceee..2bff632 100644 --- a/lagoon/validate_test.go +++ b/lagoon/validate_test.go @@ -135,6 +135,61 @@ func TestValidateBooleanNoop(t *testing.T) { } } +func TestValidateEmailConfirmedDifferentMimes(t *testing.T) { + errs, err := Validate(t.Context(), nil, nil, map[string]string{"email": "email"}, map[string]any{"email": "not-an-email"}, nil) + if err != nil { + t.Fatal(err) + } + if len(errs["email"]) == 0 { + t.Fatal("not-an-email must fail") + } + errs, err = Validate(t.Context(), nil, nil, map[string]string{"email": "email"}, map[string]any{"email": "a@b.com"}, nil) + if err != nil { + t.Fatal(err) + } + if len(errs) != 0 { + t.Fatalf("a@b.com = %v", errs) + } + + rules := map[string]string{"password": "confirmed"} + errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{ + "password": "secret", "password_confirmation": "secret", + }, nil) + if err != nil || len(errs) != 0 { + t.Fatalf("confirmed match: %v %v", errs, err) + } + errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{ + "password": "secret", "password_confirmation": "other", + }, nil) + if err != nil || len(errs["password"]) == 0 { + t.Fatalf("confirmed mismatch: %v %v", errs, err) + } + + rules = map[string]string{"password": "different:current_password"} + errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{ + "password": "same", "current_password": "same", + }, nil) + if err != nil || len(errs["password"]) == 0 { + t.Fatalf("different equal: %v %v", errs, err) + } + errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{ + "password": "new", "current_password": "old", + }, nil) + if err != nil || len(errs) != 0 { + t.Fatalf("different distinct: %v %v", errs, err) + } + + rules = map[string]string{"avatar": "mimes:jpeg,jpg,png,webp,gif"} + errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{"avatar": "PNG"}, nil) + if err != nil || len(errs) != 0 { + t.Fatalf("png: %v %v", errs, err) + } + errs, err = Validate(t.Context(), nil, nil, rules, map[string]any{"avatar": "svg"}, nil) + if err != nil || len(errs["avatar"]) == 0 { + t.Fatalf("svg: %v %v", errs, err) + } +} + func TestValidateUnrecognizedRule(t *testing.T) { _, err := Validate(t.Context(), nil, nil, map[string]string{"name": "required|nope"}, map[string]any{"name": "x"}, nil) if err == nil || !strings.Contains(err.Error(), "nope") { diff --git a/surf/locale_from_principal_test.go b/surf/locale_from_principal_test.go new file mode 100644 index 0000000..07d7716 --- /dev/null +++ b/surf/locale_from_principal_test.go @@ -0,0 +1,53 @@ +package surf + +import ( + "net/http" + "net/http/httptest" + "testing" + + "git.golem15.com/golem15/summercms/bouncer" + "git.golem15.com/golem15/summercms/towel" +) + +func TestLocaleFromPrincipal(t *testing.T) { + var got string + var ok bool + h := LocaleFromPrincipal(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + got, ok = towel.Locale(r.Context()) + })) + + ctx := towel.WithLocale(t.Context(), "en") + ctx = bouncer.WithUser(ctx, &bouncer.Principal{PreferredLocale: "pl"}) + req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) + h.ServeHTTP(httptest.NewRecorder(), req) + if !ok || got != "pl" { + t.Fatalf("locale = %q ok=%t", got, ok) + } + + got, ok = "", false + ctx = towel.WithLocale(t.Context(), "en") + ctx = bouncer.WithUser(ctx, &bouncer.Principal{}) + req = httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx) + h.ServeHTTP(httptest.NewRecorder(), req) + if !ok || got != "en" { + t.Fatalf("empty preferred locale = %q ok=%t", got, ok) + } + + got, ok = "", false + req = httptest.NewRequest(http.MethodGet, "/", nil).WithContext(towel.WithLocale(t.Context(), "de")) + h.ServeHTTP(httptest.NewRecorder(), req) + if !ok || got != "de" { + t.Fatalf("no principal locale = %q ok=%t", got, ok) + } +} + +func TestBuildRouterRegistersLocaleFromPrincipal(t *testing.T) { + r, err := BuildRouter(nil, nil) + if err != nil { + t.Fatal(err) + } + mw, ok := r.named["locale.from-principal"] + if !ok || mw.pluginID != "surf" || mw.fn == nil { + t.Fatalf("registration ok=%t mw=%+v", ok, mw) + } +}