Files
AskaEth 61cf80d5ee
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(comments): 注释中文化 · internal/runtime/memory(31 文件,纯替换)
- 只翻注释/文档字符串:820 增 / 820 删 严格 1:1;剥掉整行注释与行尾注释尾巴后,
  逐文件与 HEAD 比对「代码部分差异 = 0」;裁决/design 编号计数逐项与 HEAD 一致
- 保留可执行 SQL 样例、SQLite 报错原文、pgvector/HNSW/ANN/BLOB 等技术词原文
- 复核:go build 绿 + memory/projectindex/vector 三包测试绿;gofmt 仅剩 2 个既有未格式化文件(改动前即如此,未顺手修)
2026-10-04 13:32:54 +08:00

149 lines
4.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package memory
import (
"context"
"database/sql"
"testing"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/db"
)
// newTestDB 为需要查看原始落库时间戳文本的测试打开一个隔离的
// 内存 SQLite 库。
func newTestDB(t *testing.T) *sql.DB {
t.Helper()
ctx := context.Background()
d, err := db.Open(ctx, ":memory:", testLogger())
if err != nil {
t.Fatalf("open in-memory db: %v", err)
}
t.Cleanup(func() { _ = d.Close() })
return d
}
// insertLegacyItemSQL 用调用方给定的原始时间戳写入一行 memory_items,
// 代表定宽布局之前持久化的行。
const insertLegacyItemSQL = `INSERT INTO memory_items
(id, persona_id, role_id, project_id, scope, kind, content, tags, confidence,
source_conversation, created_at, updated_at, decay_weight)
VALUES (?, 'persona.a', '', '', 'persona', 'relationship', '旧数据', '[]', 1.0, '', ?, ?, 1.0)`
func TestFormatTime_定宽_正常(t *testing.T) {
t.Parallel()
tests := []struct {
name string
at time.Time
want string
}{
{"零小数秒", time.Date(2026, 9, 7, 3, 20, 5, 0, time.UTC), "2026-09-07T03:20:05.000000000Z"},
{"一位小数秒", time.Date(2026, 9, 7, 3, 20, 5, 830000000, time.UTC), "2026-09-07T03:20:05.830000000Z"},
{"纳秒", time.Date(2026, 9, 7, 3, 20, 5, 830978900, time.UTC), "2026-09-07T03:20:05.830978900Z"},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := formatTime(tt.at)
if got != tt.want {
t.Errorf("formatTime = %q, want %q", got, tt.want)
}
if len(got) != storedTimeLen {
t.Errorf("formatTime length = %d, want %d", len(got), storedTimeLen)
}
})
}
}
func TestFormatTime_非UTC_正常(t *testing.T) {
t.Parallel()
zone := time.FixedZone("UTC+8", 8*60*60)
// 11:20:05.830+08:00 即 03:20:05.830Z,因此落库文本是 UTC。
at := time.Date(2026, 9, 7, 11, 20, 5, 830000000, zone)
if got, want := formatTime(at), "2026-09-07T03:20:05.830000000Z"; got != want {
t.Errorf("formatTime = %q, want %q", got, want)
}
}
// TestNewStore_归一化历史时间戳_正常 直接写入一行旧的变宽数据,
// 重新打开存储触发规范化,并检查修复后的文本
// 以及再次打开时的幂等性。
func TestNewStore_归一化历史时间戳_正常(t *testing.T) {
t.Parallel()
ctx := context.Background()
d := newTestDB(t)
s, err := NewStore(ctx, d, testLogger())
if err != nil {
t.Fatalf("NewStore: %v", err)
}
// RFC3339Nano 丢弃末尾小数零,因此这个遗留值
// 只有 23 字节宽,失去了文本排序保证。
const legacy = "2026-09-07T03:20:05.83Z"
if _, err := d.ExecContext(ctx, insertLegacyItemSQL, "legacy", legacy, legacy); err != nil {
t.Fatalf("insert legacy row: %v", err)
}
if _, err := NewStore(ctx, d, testLogger()); err != nil {
t.Fatalf("NewStore (normalize): %v", err)
}
var raw string
if err := d.QueryRowContext(ctx,
`SELECT created_at FROM memory_items WHERE id = ?`, "legacy").Scan(&raw); err != nil {
t.Fatalf("read normalized created_at: %v", err)
}
const want = "2026-09-07T03:20:05.830000000Z"
if raw != want {
t.Errorf("normalized created_at = %q, want %q", raw, want)
}
got, err := s.Get(ctx, "legacy")
if err != nil {
t.Fatalf("Get: %v", err)
}
wantTime := time.Date(2026, 9, 7, 3, 20, 5, 830000000, time.UTC)
if !got.CreatedAt.Equal(wantTime) || !got.UpdatedAt.Equal(wantTime) {
t.Errorf("timestamps = %v/%v, want %v", got.CreatedAt, got.UpdatedAt, wantTime)
}
// 再次打开必须是空操作:行变为定宽后,
// 长度条件不会选中任何东西。
if _, err := NewStore(ctx, d, testLogger()); err != nil {
t.Fatalf("NewStore (second pass): %v", err)
}
var again string
if err := d.QueryRowContext(ctx,
`SELECT created_at FROM memory_items WHERE id = ?`, "legacy").Scan(&again); err != nil {
t.Fatalf("read created_at after second open: %v", err)
}
if again != raw {
t.Errorf("second open changed created_at: %q -> %q", raw, again)
}
}
// TestNewStore_无法解析的时间戳_跳过 钉住「原样保留」规则:一个坏的
// 遗留值既不该妨碍存储打开,也不该被重写。
func TestNewStore_无法解析的时间戳_跳过(t *testing.T) {
t.Parallel()
ctx := context.Background()
d := newTestDB(t)
if _, err := NewStore(ctx, d, testLogger()); err != nil {
t.Fatalf("NewStore: %v", err)
}
if _, err := d.ExecContext(ctx, insertLegacyItemSQL, "broken", "not-a-time", "not-a-time"); err != nil {
t.Fatalf("insert unparseable row: %v", err)
}
if _, err := NewStore(ctx, d, testLogger()); err != nil {
t.Fatalf("NewStore with unparseable timestamp: %v", err)
}
var raw string
if err := d.QueryRowContext(ctx,
`SELECT created_at FROM memory_items WHERE id = ?`, "broken").Scan(&raw); err != nil {
t.Fatalf("read created_at: %v", err)
}
if raw != "not-a-time" {
t.Errorf("created_at = %q, want the unparseable value left as is", raw)
}
}