Add one-way task completion and exportable pet identity cards
ci-cd / verify (push) Successful in 1m31s
ci-cd / deploy (push) Skipped

This commit is contained in:
2026-10-05 11:32:32 +08:00
parent f0ee63e642
commit cb4ffa13e9
21 changed files with 999 additions and 117 deletions
+91
View File
@@ -0,0 +1,91 @@
package httpapi
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/sundynix/pets-be/internal/config"
"github.com/sundynix/pets-be/internal/database"
"github.com/sundynix/pets-be/internal/model"
)
func TestTaskCompletionCannotBeUndone(t *testing.T) {
gin.SetMode(gin.TestMode)
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest(http.MethodPut, "/completion", bytes.NewBufferString(`{"date":"2026-10-05","completed":false}`))
api := New(nil, nil, &config.Config{})
api.setTaskCompletion(c)
if rec.Code != http.StatusBadRequest {
t.Fatalf("undo status %d", rec.Code)
}
}
// All fixtures and writes are rolled back, including inner batch transactions.
func TestTaskCompletionPostgres(t *testing.T) {
if os.Getenv("PETS_ADMIN_INTEGRATION") != "1" {
t.Skip("PostgreSQL integration disabled")
}
cfg, err := config.Load()
if err != nil {
t.Fatal(err)
}
db, err := database.Open(cfg.Postgres, true)
if err != nil {
t.Fatal(err)
}
sqlDB, _ := db.DB()
defer sqlDB.Close()
tx := db.Begin()
if tx.Error != nil {
t.Fatal(tx.Error)
}
defer tx.Rollback()
user := model.User{OpenID: "completion-test:" + uuid.NewString(), Nickname: "测试家长"}
if err := tx.Create(&user).Error; err != nil {
t.Fatal(err)
}
pet := model.Pet{UserID: user.ID, Name: "测试毛孩", Species: "dog", Gender: "male"}
if err := tx.Create(&pet).Error; err != nil {
t.Fatal(err)
}
tasks := []model.CareTask{{UserID: user.ID, PetID: pet.ID, Title: "喝水", Category: "water", TimeOfDay: "08:00", RepeatRule: "daily"}, {UserID: user.ID, PetID: pet.ID, Title: "喂食", Category: "food", TimeOfDay: "09:00", RepeatRule: "daily"}}
if err := tx.Create(&tasks).Error; err != nil {
t.Fatal(err)
}
api := New(tx, nil, cfg)
call := func(ids []string) int {
t.Helper()
body, _ := json.Marshal(gin.H{"date": "2026-10-05", "task_ids": ids})
rec := httptest.NewRecorder()
c, _ := gin.CreateTestContext(rec)
c.Set("actor_id", user.ID)
c.Request = httptest.NewRequest(http.MethodPut, "/tasks/completions", bytes.NewReader(body))
api.completeTasks(c)
return rec.Code
}
if status := call([]string{tasks[0].ID, uuid.NewString()}); status != 400 {
t.Fatalf("invalid batch status %d", status)
}
var count int64
tx.Model(&model.TaskCompletion{}).Where("user_id = ?", user.ID).Count(&count)
if count != 0 {
t.Fatal("invalid batch partially completed")
}
ids := []string{tasks[0].ID, tasks[1].ID}
for range 2 {
if status := call(ids); status != 200 {
t.Fatalf("batch status %d", status)
}
}
tx.Model(&model.TaskCompletion{}).Where("user_id = ?", user.ID).Count(&count)
if count != 2 {
t.Fatalf("retry duplicated completions: %d", count)
}
}