capsule AI-native Unix-like composition layer

src/server/internal/character/store_test.go

4,156 bytes · 159 lines · capsule://quake0day/[email protected] raw on github

package character

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestLoadLegacyCharacterStripsAvatarModelOnSave(t *testing.T) {
	baseDir := t.TempDir()
	charID := "123e4567-e89b-12d3-a456-426614174000"
	charDir := filepath.Join(baseDir, charDirName("Legacy", charID))
	if err := os.MkdirAll(charDir, 0755); err != nil {
		t.Fatal(err)
	}

	legacy := map[string]any{
		"id":              charID,
		"name":            "Legacy",
		"description":     "legacy payload",
		"avatar_image":    "",
		"use_face_crop":   false,
		"voice_provider":  "doubao",
		"voice_type":      "温柔文雅",
		"avatar_model":    "flash_head",
		"speaking_style":  "平静",
		"personality":     "稳定",
		"welcome_message": "你好",
		"system_prompt":   "legacy system prompt",
		"tags":            []string{"legacy"},
		"images":          []any{},
		"active_image":    "",
		"image_mode":      "fixed",
		"created_at":      "2026-04-18T00:00:00Z",
		"updated_at":      "2026-04-18T00:00:00Z",
	}
	data, err := json.MarshalIndent(legacy, "", "  ")
	if err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(charDir, "character.json"), data, 0644); err != nil {
		t.Fatal(err)
	}

	store, err := NewStore(baseDir)
	if err != nil {
		t.Fatal(err)
	}

	char, err := store.Get(charID)
	if err != nil {
		t.Fatal(err)
	}
	if char.Name != "Legacy" {
		t.Fatalf("expected Legacy, got %q", char.Name)
	}

	updated := &Character{
		Name:           char.Name,
		Description:    "updated legacy payload",
		AvatarImage:    char.AvatarImage,
		UseFaceCrop:    char.UseFaceCrop,
		VoiceProvider:  char.VoiceProvider,
		VoiceType:      char.VoiceType,
		SpeakingStyle:  char.SpeakingStyle,
		Personality:    char.Personality,
		WelcomeMessage: char.WelcomeMessage,
		SystemPrompt:   char.SystemPrompt,
		Tags:           append([]string(nil), char.Tags...),
		ImageMode:      char.ImageMode,
	}
	if _, err := store.Update(charID, updated); err != nil {
		t.Fatal(err)
	}

	saved, err := os.ReadFile(filepath.Join(charDir, "character.json"))
	if err != nil {
		t.Fatal(err)
	}
	if strings.Contains(string(saved), "\"avatar_model\"") {
		t.Fatalf("expected saved character.json to omit avatar_model, got %s", string(saved))
	}

	var savedJSON map[string]any
	if err := json.Unmarshal(saved, &savedJSON); err != nil {
		t.Fatal(err)
	}
	if _, ok := savedJSON["avatar_model"]; ok {
		t.Fatalf("expected avatar_model to be removed from saved JSON")
	}
}

func TestIdleVideoFilenameIncludesResolutionVariant(t *testing.T) {
	store, err := NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}

	got := store.IdleVideoFilename("img_003.png", DefaultIdleVideoProfile)
	want := "img_003__breathing10s_v1.mp4"
	if got != want {
		t.Fatalf("expected %q, got %q", want, got)
	}
}

func TestActivateImageMovesImageFirstAndUpdatesAvatarCover(t *testing.T) {
	store, err := NewStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}

	char, err := store.Create(&Character{
		Name:      "Avatar Order",
		VoiceType: "温柔文雅",
	})
	if err != nil {
		t.Fatal(err)
	}

	for _, filename := range []string{"img_001.png", "img_002.png", "img_003.png"} {
		if err := store.AddImage(char.ID, ImageInfo{
			Filename: filename,
			OrigName: filename,
			AddedAt:  "1",
		}); err != nil {
			t.Fatal(err)
		}
	}

	if err := store.ActivateImage(char.ID, "img_003.png"); err != nil {
		t.Fatal(err)
	}

	updated, err := store.Get(char.ID)
	if err != nil {
		t.Fatal(err)
	}
	if updated.ActiveImage != "img_003.png" {
		t.Fatalf("expected active image img_003.png, got %q", updated.ActiveImage)
	}
	wantCover := "/api/v1/characters/" + char.ID + "/images/img_003.png"
	if updated.AvatarImage != wantCover {
		t.Fatalf("expected avatar cover %q, got %q", wantCover, updated.AvatarImage)
	}
	if len(updated.Images) == 0 || updated.Images[0].Filename != "img_003.png" {
		t.Fatalf("expected active image first in stored order, got %#v", updated.Images)
	}

	imgs, err := store.ListImages(char.ID)
	if err != nil {
		t.Fatal(err)
	}
	if len(imgs) != 3 || imgs[0].Filename != "img_003.png" {
		t.Fatalf("expected active image first in list response, got %#v", imgs)
	}
}