|
@@ -0,0 +1,151 @@
|
|
|
|
|
+package repository
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "sync"
|
|
|
|
|
+ "testing"
|
|
|
|
|
+ "time"
|
|
|
|
|
+
|
|
|
|
|
+ "job-cheng-xing/model"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+func strPtr(s string) *string { return &s }
|
|
|
|
|
+
|
|
|
|
|
+func TestMemoryRepo_CreateAndFind(t *testing.T) {
|
|
|
|
|
+ repo := NewMemoryRepo()
|
|
|
|
|
+ order := &model.Order{
|
|
|
|
|
+ ID: "ord_001",
|
|
|
|
|
+ Status: model.StatusPending,
|
|
|
|
|
+ ServiceTime: time.Now(),
|
|
|
|
|
+ Duration: 120,
|
|
|
|
|
+ Address: "北京朝阳",
|
|
|
|
|
+ CreatedAt: time.Now(),
|
|
|
|
|
+ UpdatedAt: time.Now(),
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ err := repo.Create(order)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("Create failed: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ found, err := repo.FindByID("ord_001")
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("FindByID failed: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ if found.ID != "ord_001" {
|
|
|
|
|
+ t.Errorf("expected ord_001, got %s", found.ID)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestMemoryRepo_FindByID_NotFound(t *testing.T) {
|
|
|
|
|
+ repo := NewMemoryRepo()
|
|
|
|
|
+ _, err := repo.FindByID("nonexistent")
|
|
|
|
|
+ if err != model.ErrNotFound {
|
|
|
|
|
+ t.Errorf("expected ErrNotFound, got %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestMemoryRepo_Update_CAS_Success(t *testing.T) {
|
|
|
|
|
+ repo := NewMemoryRepo()
|
|
|
|
|
+ order := &model.Order{
|
|
|
|
|
+ ID: "ord_001",
|
|
|
|
|
+ Status: model.StatusPending,
|
|
|
|
|
+ ServiceTime: time.Now(),
|
|
|
|
|
+ Duration: 120,
|
|
|
|
|
+ Address: "北京朝阳",
|
|
|
|
|
+ CreatedAt: time.Now(),
|
|
|
|
|
+ UpdatedAt: time.Now(),
|
|
|
|
|
+ }
|
|
|
|
|
+ repo.Create(order)
|
|
|
|
|
+
|
|
|
|
|
+ newOrder := &model.Order{
|
|
|
|
|
+ Status: model.StatusAccepted,
|
|
|
|
|
+ ProviderID: strPtr("prov_001"),
|
|
|
|
|
+ UpdatedAt: time.Now(),
|
|
|
|
|
+ }
|
|
|
|
|
+ err := repo.Update("ord_001", model.StatusPending, newOrder)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("Update failed: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ found, _ := repo.FindByID("ord_001")
|
|
|
|
|
+ if found.Status != model.StatusAccepted {
|
|
|
|
|
+ t.Errorf("expected accepted, got %s", found.Status)
|
|
|
|
|
+ }
|
|
|
|
|
+ if *found.ProviderID != "prov_001" {
|
|
|
|
|
+ t.Errorf("expected prov_001, got %s", *found.ProviderID)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestMemoryRepo_Update_CAS_Fail_WrongStatus(t *testing.T) {
|
|
|
|
|
+ repo := NewMemoryRepo()
|
|
|
|
|
+ order := &model.Order{
|
|
|
|
|
+ ID: "ord_001",
|
|
|
|
|
+ Status: model.StatusPending,
|
|
|
|
|
+ }
|
|
|
|
|
+ repo.Create(order)
|
|
|
|
|
+
|
|
|
|
|
+ newOrder := &model.Order{Status: model.StatusCanceled}
|
|
|
|
|
+ err := repo.Update("ord_001", model.StatusAccepted, newOrder)
|
|
|
|
|
+ if err != model.ErrStatusConflict {
|
|
|
|
|
+ t.Errorf("expected ErrStatusConflict, got %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestMemoryRepo_ConcurrentAccept(t *testing.T) {
|
|
|
|
|
+ repo := NewMemoryRepo()
|
|
|
|
|
+ order := &model.Order{
|
|
|
|
|
+ ID: "ord_001",
|
|
|
|
|
+ Status: model.StatusPending,
|
|
|
|
|
+ ServiceTime: time.Now(),
|
|
|
|
|
+ Duration: 120,
|
|
|
|
|
+ Address: "北京朝阳",
|
|
|
|
|
+ CreatedAt: time.Now(),
|
|
|
|
|
+ UpdatedAt: time.Now(),
|
|
|
|
|
+ }
|
|
|
|
|
+ repo.Create(order)
|
|
|
|
|
+
|
|
|
|
|
+ const numGoroutines = 50
|
|
|
|
|
+ var wg sync.WaitGroup
|
|
|
|
|
+ results := make(chan error, numGoroutines)
|
|
|
|
|
+
|
|
|
|
|
+ for i := 0; i < numGoroutines; i++ {
|
|
|
|
|
+ wg.Add(1)
|
|
|
|
|
+ go func(idx int) {
|
|
|
|
|
+ defer wg.Done()
|
|
|
|
|
+ pid := "prov_" + string(rune('a'+idx%26)) + string(rune('0'+idx/26))
|
|
|
|
|
+ newOrder := &model.Order{
|
|
|
|
|
+ Status: model.StatusAccepted,
|
|
|
|
|
+ ProviderID: &pid,
|
|
|
|
|
+ UpdatedAt: time.Now(),
|
|
|
|
|
+ }
|
|
|
|
|
+ results <- repo.Update("ord_001", model.StatusPending, newOrder)
|
|
|
|
|
+ }(i)
|
|
|
|
|
+ }
|
|
|
|
|
+ wg.Wait()
|
|
|
|
|
+ close(results)
|
|
|
|
|
+
|
|
|
|
|
+ successCount := 0
|
|
|
|
|
+ failCount := 0
|
|
|
|
|
+ for err := range results {
|
|
|
|
|
+ if err == nil {
|
|
|
|
|
+ successCount++
|
|
|
|
|
+ } else if err == model.ErrStatusConflict {
|
|
|
|
|
+ failCount++
|
|
|
|
|
+ } else {
|
|
|
|
|
+ t.Errorf("unexpected error: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if successCount != 1 {
|
|
|
|
|
+ t.Errorf("expected exactly 1 success, got %d", successCount)
|
|
|
|
|
+ }
|
|
|
|
|
+ if failCount != numGoroutines-1 {
|
|
|
|
|
+ t.Errorf("expected %d failures, got %d", numGoroutines-1, failCount)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ found, _ := repo.FindByID("ord_001")
|
|
|
|
|
+ if found.ProviderID == nil {
|
|
|
|
|
+ t.Fatal("expected ProviderID to be set")
|
|
|
|
|
+ }
|
|
|
|
|
+ t.Logf("winner provider: %s", *found.ProviderID)
|
|
|
|
|
+}
|