636a3bbf2f
服务端记账从「账户」细到「每设备」(每设备独立 dp_uuid),配额单位分钟→GB 按账户综合卡控。000015_per_device_usage 迁移(mysql+sqlite 双份):devices.dp_uuid + usage_device_daily 表 + plans.daily_mb。handler_grpc 按 dp_uuid 回映射 (user_id,device_id) 双写账户+每设备;usage 服务/handler 暴露 /v1/usage(/devices)。 含 sqlite_per_device / usage handler 测试。 注:此迁移 prod 已 migrate up 运行、部署二进制已内嵌;本次补提交使 git 与线上一致。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
47 lines
1.5 KiB
Go
47 lines
1.5 KiB
Go
package usage
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/wangjia/pangolin/server/internal/codes"
|
|
)
|
|
|
|
// authedRequest builds a request whose context carries an authenticated user id,
|
|
// mirroring what auth.RequireAuth installs upstream.
|
|
func authedRequest(method, target string, userID int64) *http.Request {
|
|
r := httptest.NewRequest(method, target, nil)
|
|
if userID != 0 {
|
|
r = r.WithContext(context.WithValue(r.Context(), codes.CtxKeyUserID, userID))
|
|
}
|
|
return r
|
|
}
|
|
|
|
// The DeviceUsageHandler gates on auth, method and the days query param before
|
|
// ever touching the service/DB — these branches are verified DB-free.
|
|
func TestDeviceUsageHandler_Guards(t *testing.T) {
|
|
h := NewDeviceUsageHandler(NewService(nil, nil, nil, 0))
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *http.Request
|
|
status int
|
|
}{
|
|
{"no auth → 401", authedRequest(http.MethodGet, "/v1/usage/devices", 0), http.StatusUnauthorized},
|
|
{"wrong method → 405", authedRequest(http.MethodPost, "/v1/usage/devices", 7), http.StatusMethodNotAllowed},
|
|
{"days out of range → 400", authedRequest(http.MethodGet, "/v1/usage/devices?days=999", 7), http.StatusBadRequest},
|
|
{"days non-numeric → 400", authedRequest(http.MethodGet, "/v1/usage/devices?days=abc", 7), http.StatusBadRequest},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, c.req)
|
|
if w.Code != c.status {
|
|
t.Errorf("status=%d, want %d (body=%s)", w.Code, c.status, w.Body.String())
|
|
}
|
|
})
|
|
}
|
|
}
|