40760aa884
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调 - desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导) - android:Compose 主 App + IME(键盘内录音直传) - ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写) - design/design-pipeline:设计系统 + token 导出 iOS/Android 主题 - doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
2.1 KiB
Go
81 lines
2.1 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/alicebob/miniredis/v2"
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
func rdb(t *testing.T) *redis.Client {
|
|
t.Helper()
|
|
mr := miniredis.RunT(t)
|
|
return redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
|
}
|
|
|
|
func TestAllowSessionWindow(t *testing.T) {
|
|
r := rdb(t)
|
|
ctx := context.Background()
|
|
now := time.Now()
|
|
// 30 次放行,第 31 次拒绝
|
|
for i := 0; i < 30; i++ {
|
|
ok, err := AllowSession(ctx, r, "dev1", now)
|
|
if err != nil || !ok {
|
|
t.Fatalf("session %d should pass: ok=%v err=%v", i+1, ok, err)
|
|
}
|
|
}
|
|
if ok, _ := AllowSession(ctx, r, "dev1", now); ok {
|
|
t.Fatal("31st session should be rejected")
|
|
}
|
|
// 其他设备不受影响
|
|
if ok, _ := AllowSession(ctx, r, "dev2", now); !ok {
|
|
t.Fatal("other device should pass")
|
|
}
|
|
}
|
|
|
|
func TestAllowAudioSecondsWindow(t *testing.T) {
|
|
r := rdb(t)
|
|
ctx := context.Background()
|
|
now := time.Now()
|
|
// 1700s 放行
|
|
if ok, _ := AllowAudioSeconds(ctx, r, "dev1", 1700, now); !ok {
|
|
t.Fatal("1700s should pass")
|
|
}
|
|
// 再 100s(累计 1800)放行
|
|
if ok, _ := AllowAudioSeconds(ctx, r, "dev1", 100, now.Add(time.Second)); !ok {
|
|
t.Fatal("cumulative 1800s should pass")
|
|
}
|
|
// 再 1s 超限拒绝
|
|
if ok, _ := AllowAudioSeconds(ctx, r, "dev1", 1, now.Add(2*time.Second)); ok {
|
|
t.Fatal("1801s should be rejected")
|
|
}
|
|
}
|
|
|
|
func TestDeviceSlot(t *testing.T) {
|
|
r := rdb(t)
|
|
ctx := context.Background()
|
|
ok, err := AcquireDeviceSlot(ctx, r, "dev1", "s1")
|
|
if err != nil || !ok {
|
|
t.Fatalf("first acquire should pass: %v", err)
|
|
}
|
|
if ok, _ := AcquireDeviceSlot(ctx, r, "dev1", "s2"); ok {
|
|
t.Fatal("second concurrent acquire should fail")
|
|
}
|
|
// 非持有者释放无效
|
|
if err := ReleaseDeviceSlot(ctx, r, "dev1", "s2"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ok, _ := AcquireDeviceSlot(ctx, r, "dev1", "s3"); ok {
|
|
t.Fatal("slot should still be held by s1")
|
|
}
|
|
// 持有者释放后可再获取
|
|
if err := ReleaseDeviceSlot(ctx, r, "dev1", "s1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ok, _ := AcquireDeviceSlot(ctx, r, "dev1", "s3"); !ok {
|
|
t.Fatal("acquire after release should pass")
|
|
}
|
|
}
|