package server import ( "encoding/json" "net/http/httptest" "strings" "testing" ) // GetSetting 对"键不存在"和"读取出错"的返回值只差一个 error:两种情况 value 都是 // 空串。密码相关的 handler 一旦把 error 当成"还没设置密码",就会在数据库抖动期间 // 敞开初始化入口——authInit 会放行一个未认证请求去覆盖已有的管理员密码, // authStatus 则会把前端直接送到 /setup 去照着做这件事。 // // 这两个测试把 handler 的连接池关掉来制造读取失败,断言两处都 fail closed。 func TestAuthStatusFailsClosedWhenDataSourceUnavailable(t *testing.T) { m, err := NewManager(t.TempDir(), "") if err != nil { t.Skipf("postgres unavailable (%v)", err) } defer m.Close() // 关掉池子,让后续 GetSetting 返回 error 而不是 sql.ErrNoRows。 if err := m.pg.Close(); err != nil { t.Fatal(err) } s := &Server{m: m} w := httptest.NewRecorder() s.authStatus(w, httptest.NewRequest("GET", "/api/auth/status", nil)) if w.Code != 503 { t.Fatalf("status=%d want 503 (读失败被当成未初始化会把用户送去 /setup 覆盖密码); body=%s", w.Code, w.Body.String()) } var payload struct { Initialized *bool `json:"initialized"` } if err := json.Unmarshal(w.Body.Bytes(), &payload); err == nil && payload.Initialized != nil { t.Fatalf("读失败时不应回答 initialized,得到 %v", *payload.Initialized) } } func TestAuthInitFailsClosedWhenDataSourceUnavailable(t *testing.T) { m, err := NewManager(t.TempDir(), "") if err != nil { t.Skipf("postgres unavailable (%v)", err) } defer m.Close() if err := m.pg.Close(); err != nil { t.Fatal(err) } s := &Server{m: m} w := httptest.NewRecorder() body := strings.NewReader(`{"password":"correct horse battery"}`) s.authInit(w, httptest.NewRequest("POST", "/api/auth/init", body)) if w.Code != 503 { t.Fatalf("status=%d want 503 (读失败时放行会让未认证请求覆盖已有密码); body=%s", w.Code, w.Body.String()) } if strings.Contains(w.Body.String(), "token") { t.Fatalf("读失败时不应签发 token: %s", w.Body.String()) } } func TestValidatePassword(t *testing.T) { for _, tc := range []struct { name, pw string wantErr bool }{ {"空", "", true}, {"七位", "1234567", true}, {"八位", "12345678", false}, {"八个汉字按字符数而非字节数计", "密码密码密码密码", false}, {"三个汉字够 9 字节但只有 3 个字符", "密码强", true}, {"72 字节", strings.Repeat("a", 72), false}, {"73 字节超出 bcrypt 上限", strings.Repeat("a", 73), true}, } { t.Run(tc.name, func(t *testing.T) { if got := validatePassword(tc.pw); (got != "") != tc.wantErr { t.Fatalf("validatePassword(%q)=%q, wantErr=%v", tc.pw, got, tc.wantErr) } }) } }