Files
ONE/backend/internal/api/api_test.go
T
Sakurasan e68400b389 系统设置迁入后台:站点地址 / OAuth 凭据 / R2 存储 / 管理员账号,保存即生效无需重启
- 生效规则统一为「后台填了用后台的,没填回落环境变量」,老部署不改 env 照常跑
- config.Resolver:短缓存解析有效配置,存储后端按配置签名热重建;后台保存主动失效
- 秘密项(client secret / bot token / R2 密钥 / 管理员密码)接口永不回显明文,
  只报「是否已配置、来自哪里」;留空保存 = 保持现值
- 管理员密码 bcrypt 入库,DB 哈希优先、显式设置的 env 密码作解锁后路;
  后台改过密码后 admin/admin 开发模式立即失效
- 设置页新增「登录与存储」标签,基础信息加站点地址;秘密项带来源提示
- 监听地址 / 数据库 / 目录 / ONE_SECRET / Passkey 仍留环境变量(启动期依赖)
2026-10-01 12:14:10 +08:00

97 lines
2.8 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"oneblog/internal/auth"
"oneblog/internal/config"
"oneblog/internal/db"
"oneblog/internal/model"
"oneblog/internal/store"
)
func newTestAPI(t *testing.T) (*API, http.Handler) {
t.Helper()
d, err := db.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
t.Cleanup(func() { d.Close() })
st, err := store.New(d)
if err != nil {
t.Fatalf("store: %v", err)
}
a := &API{
Store: st,
Cfg: &config.Config{SiteURL: "http://localhost:8080",
TelegramBot: "testbot", TelegramToken: "123:abc"},
ReaderSessions: auth.NewReaderSessions("test-secret", time.Hour),
}
settings, err := st.GetSettings()
if err != nil {
t.Fatalf("settings: %v", err)
}
settings.CommentsEnabled = true
if err := st.UpdateSettings(settings); err != nil {
t.Fatalf("enable comments: %v", err)
}
return a, a.Routes()
}
// Telegram 伪造签名反复重试应触发 IP 限速
func TestTelegramAuthRateLimited(t *testing.T) {
_, h := newTestAPI(t)
body := `{"id":1,"first_name":"x","hash":"deadbeef"}`
var rec *httptest.ResponseRecorder
for i := 0; i < maxAuthFails; i++ {
req := httptest.NewRequest(http.MethodPost, "/api/auth/telegram", strings.NewReader(body))
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("attempt %d: got %d, want 403", i+1, rec.Code)
}
}
req := httptest.NewRequest(http.MethodPost, "/api/auth/telegram", strings.NewReader(body))
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusTooManyRequests {
t.Fatalf("after %d failures: got %d, want 429", maxAuthFails, rec.Code)
}
}
// 评论写入按读者限速:第 maxComments+1 条被拒
func TestCommentRateLimited(t *testing.T) {
a, h := newTestAPI(t)
p, err := a.Store.Create(model.PostInput{Kind: model.KindLong, Title: "t", Slug: "t",
ContentMd: "x", Status: model.StatusPublished})
if err != nil {
t.Fatal(err)
}
rd, err := a.Store.UpsertReader(model.Reader{Provider: "github", Handle: "u1", Name: "u1"})
if err != nil {
t.Fatal(err)
}
tok, _ := a.ReaderSessions.Issue(rd.ID)
post := func(i int) *httptest.ResponseRecorder {
body, _ := json.Marshal(map[string]any{"post_id": p.ID, "body_md": "好"})
req := httptest.NewRequest(http.MethodPost, "/api/comments", strings.NewReader(string(body)))
req.AddCookie(&http.Cookie{Name: auth.ReaderCookie, Value: tok})
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
return rec
}
for i := 0; i < maxComments; i++ {
if rec := post(i); rec.Code != http.StatusCreated {
t.Fatalf("comment %d: got %d %s", i+1, rec.Code, rec.Body.String())
}
}
if rec := post(maxComments); rec.Code != http.StatusTooManyRequests {
t.Fatalf("over limit: got %d, want 429", rec.Code)
}
}