mirror of
https://github.com/zgs225/cliproxy-plugin-commandcode.git
synced 2026-09-26 11:42:48 +08:00
223 lines
5.7 KiB
Go
223 lines
5.7 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestFlexibleTime(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantYear int
|
|
}{
|
|
{
|
|
name: "unix seconds",
|
|
input: `{"resetAt": 1741123456}`,
|
|
wantYear: 2025,
|
|
},
|
|
{
|
|
name: "unix milliseconds",
|
|
input: `{"resetAt": 1741123456000}`,
|
|
wantYear: 2025,
|
|
},
|
|
{
|
|
name: "RFC3339 string",
|
|
input: `{"resetAt": "2025-06-15T12:00:00Z"}`,
|
|
wantYear: 2025,
|
|
},
|
|
{
|
|
name: "null",
|
|
input: `{"resetAt": null}`,
|
|
wantYear: 1, // Zero time year
|
|
},
|
|
{
|
|
name: "empty string",
|
|
input: `{"resetAt": ""}`,
|
|
wantYear: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var res struct {
|
|
ResetAt FlexibleTime `json:"resetAt"`
|
|
}
|
|
if err := json.Unmarshal([]byte(tt.input), &res); err != nil {
|
|
t.Fatalf("Unmarshal error: %v", err)
|
|
}
|
|
if res.ResetAt.Year() != tt.wantYear {
|
|
t.Errorf("Year = %d, want %d", res.ResetAt.Year(), tt.wantYear)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseAndFormatUsage(t *testing.T) {
|
|
raw := []byte(`{
|
|
"credits": {
|
|
"monthlyCredits": 1000.0,
|
|
"opensourceMonthlyCredits": 500.0,
|
|
"extraBonus": 50.0
|
|
},
|
|
"windowLimits": {
|
|
"fiveHour": {
|
|
"used": 25.0,
|
|
"cap": 100.0,
|
|
"exceeded": false,
|
|
"resetAt": 1741123456
|
|
},
|
|
"weekly": {
|
|
"used": 200.0,
|
|
"cap": 1000.0,
|
|
"exceeded": false,
|
|
"resetAt": "2025-03-10T12:00:00Z"
|
|
}
|
|
}
|
|
}`)
|
|
|
|
now := time.Date(2025, 3, 4, 12, 0, 0, 0, time.UTC)
|
|
usage, err := ParseAndFormatUsage(raw, now)
|
|
if err != nil {
|
|
t.Fatalf("ParseAndFormatUsage error: %v", err)
|
|
}
|
|
|
|
if !usage.OK {
|
|
t.Fatal("expected OK=true")
|
|
}
|
|
|
|
// Verify credits
|
|
if usage.Credits.MonthlyCredits != 1000.0 {
|
|
t.Errorf("MonthlyCredits = %v, want 1000", usage.Credits.MonthlyCredits)
|
|
}
|
|
if usage.Credits.OpensourceMonthlyCredits != 500.0 {
|
|
t.Errorf("OpensourceMonthlyCredits = %v, want 500", usage.Credits.OpensourceMonthlyCredits)
|
|
}
|
|
if usage.Credits.TotalCredits != 1500.0 {
|
|
t.Errorf("TotalCredits = %v, want 1500", usage.Credits.TotalCredits)
|
|
}
|
|
|
|
// Verify 5-hour window
|
|
fiveHour := usage.WindowLimits.FiveHour
|
|
if fiveHour.Used != 25.0 {
|
|
t.Errorf("FiveHour Used = %v, want 25", fiveHour.Used)
|
|
}
|
|
if fiveHour.Cap != 100.0 {
|
|
t.Errorf("FiveHour Cap = %v, want 100", fiveHour.Cap)
|
|
}
|
|
if fiveHour.Remaining != 75.0 {
|
|
t.Errorf("FiveHour Remaining = %v, want 75", fiveHour.Remaining)
|
|
}
|
|
if fiveHour.Percentage != 25.0 {
|
|
t.Errorf("FiveHour Percentage = %v, want 25", fiveHour.Percentage)
|
|
}
|
|
if fiveHour.Exceeded {
|
|
t.Errorf("FiveHour Exceeded = true, want false")
|
|
}
|
|
|
|
// Verify weekly window
|
|
weekly := usage.WindowLimits.Weekly
|
|
if weekly.Used != 200.0 {
|
|
t.Errorf("Weekly Used = %v, want 200", weekly.Used)
|
|
}
|
|
if weekly.Cap != 1000.0 {
|
|
t.Errorf("Weekly Cap = %v, want 1000", weekly.Cap)
|
|
}
|
|
if weekly.Remaining != 800.0 {
|
|
t.Errorf("Weekly Remaining = %v, want 800", weekly.Remaining)
|
|
}
|
|
if weekly.Percentage != 20.0 {
|
|
t.Errorf("Weekly Percentage = %v, want 20", weekly.Percentage)
|
|
}
|
|
if weekly.ResetAt != "2025-03-10T12:00:00Z" {
|
|
t.Errorf("Weekly ResetAt = %v, want 2025-03-10T12:00:00Z", weekly.ResetAt)
|
|
}
|
|
}
|
|
|
|
func TestFetchCreditsRaw_FallbackHTTP(t *testing.T) {
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/internal/billing/credits" {
|
|
t.Errorf("unexpected path: %s", r.URL.Path)
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
cookie := r.Header.Get("Cookie")
|
|
expectedCookie := "__Secure-commandcode_prod_.session_token=test-session-123"
|
|
if cookie != expectedCookie {
|
|
t.Errorf("Cookie = %q, want %q", cookie, expectedCookie)
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"credits":{"monthlyCredits":100},"windowLimits":{"fiveHour":{"used":1,"cap":10}}}`))
|
|
}))
|
|
defer ts.Close()
|
|
|
|
// Ensure hostCaller is nil for fallback test
|
|
SetHostCaller(nil)
|
|
SetDefaultHTTPClient(ts.Client())
|
|
|
|
body, status, err := FetchCreditsRaw(context.Background(), ts.URL, "test-session-123", "")
|
|
if err != nil {
|
|
t.Fatalf("FetchCreditsRaw error: %v", err)
|
|
}
|
|
if status != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", status)
|
|
}
|
|
if len(body) == 0 {
|
|
t.Fatal("expected non-empty body")
|
|
}
|
|
|
|
usage, errParse := ParseAndFormatUsage(body, time.Time{})
|
|
if errParse != nil {
|
|
t.Fatalf("ParseAndFormatUsage error: %v", errParse)
|
|
}
|
|
if usage.Credits.MonthlyCredits != 100 {
|
|
t.Errorf("MonthlyCredits = %v, want 100", usage.Credits.MonthlyCredits)
|
|
}
|
|
}
|
|
|
|
func TestFetchCreditsRaw_MissingToken(t *testing.T) {
|
|
_, status, err := FetchCreditsRaw(context.Background(), "", "", "")
|
|
if err == nil {
|
|
t.Fatal("expected error for missing token")
|
|
}
|
|
if status != http.StatusBadRequest {
|
|
t.Errorf("status = %d, want 400", status)
|
|
}
|
|
}
|
|
|
|
func TestFetchCreditsRaw_HostCaller(t *testing.T) {
|
|
mockResponsePayload := []byte(`{"credits":{"monthlyCredits":500},"windowLimits":{"fiveHour":{"used":5,"cap":50}}}`)
|
|
|
|
SetHostCaller(func(method string, payload []byte) ([]byte, error) {
|
|
if method != "host.http.do" {
|
|
t.Errorf("method = %s, want host.http.do", method)
|
|
}
|
|
hostResp := HostHTTPResponse{
|
|
StatusCode: http.StatusOK,
|
|
Body: mockResponsePayload,
|
|
}
|
|
respJSON, _ := json.Marshal(hostResp)
|
|
return json.Marshal(Envelope{OK: true, Result: respJSON})
|
|
})
|
|
defer SetHostCaller(nil)
|
|
|
|
body, status, err := FetchCreditsRaw(context.Background(), "https://api.commandcode.ai", "mock-token", "cb-123")
|
|
if err != nil {
|
|
t.Fatalf("FetchCreditsRaw with hostCaller error: %v", err)
|
|
}
|
|
if status != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", status)
|
|
}
|
|
if string(body) != string(mockResponsePayload) {
|
|
t.Errorf("body = %s, want %s", string(body), string(mockResponsePayload))
|
|
}
|
|
}
|