package abono_test import ( "context" "errors" "testing" "time" "gorm.io/gorm" "amadan.net/rastrillo/abono" ) func TestCompAndEndCompCallOnChangeInTheSameTransaction(t *testing.T) { now := t0 var calls []string var seen int64 = -1 var failWith error s, db := newService(t, &now, abono.Config{ OnChange: func(ctx context.Context, tx *gorm.DB, subject string) error { calls = append(calls, subject) // Same transaction: the comp written a moment ago is visible // here, though it is not committed yet. if err := tx.Table("abono_comps").Where("subject = ? AND status = 'active'", subject).Count(&seen).Error; err != nil { return err } return failWith }, }) ctx := context.Background() id, err := s.Comp(ctx, "s1", "annual", nil, "for the editor") if err != nil { t.Fatalf("Comp: %v", err) } if id <= 0 { t.Errorf("Comp id = %d", id) } if len(calls) != 1 || calls[0] != "s1" || seen != 1 { t.Errorf("OnChange calls = %v, active comps seen inside = %d; want [s1] and 1", calls, seen) } if got := db.dump("SELECT subject, plan, status, ends_at, note FROM abono_comps"); got != "s1|annual|active||for the editor\n" { t.Errorf("comps = %q", got) } // A hook error rolls Comp back. failWith = errors.New("hook failed") if _, err := s.Comp(ctx, "s2", "annual", nil, ""); !errors.Is(err, failWith) { t.Errorf("Comp with a failing hook: %v, want the hook's error", err) } if got := db.dump("SELECT subject FROM abono_comps WHERE subject = 's2'"); got != "" { t.Errorf("a comp survived its hook's failure: %q", got) } // And rolls EndComp back. if err := s.EndComp(ctx, id); !errors.Is(err, failWith) { t.Errorf("EndComp with a failing hook: %v, want the hook's error", err) } if got := db.dump("SELECT status FROM abono_comps WHERE id = ?", id); got != "active\n" { t.Errorf("status after a rolled-back EndComp = %q", got) } failWith = nil calls = nil now = t0.Add(time.Hour) if err := s.EndComp(ctx, id); err != nil { t.Fatalf("EndComp: %v", err) } if len(calls) != 1 || calls[0] != "s1" { t.Errorf("OnChange calls after EndComp = %v", calls) } if got := db.dump("SELECT status FROM abono_comps WHERE id = ?", id); got != "canceled\n" { t.Errorf("status = %q", got) } if has, err := s.HasPlan(ctx, "s1", "annual"); err != nil || has { t.Errorf("HasPlan after EndComp = %v, %v", has, err) } // Ending it again changes nothing, so tells nobody. calls = nil if err := s.EndComp(ctx, id); err != nil { t.Errorf("EndComp again: %v", err) } if len(calls) != 0 { t.Errorf("EndComp on an ended comp called OnChange: %v", calls) } if err := s.EndComp(ctx, id+100); !errors.Is(err, abono.ErrNotFound) { t.Errorf("EndComp of a missing comp: %v, want ErrNotFound", err) } } func TestCompRefusesNoSubjectOrPlan(t *testing.T) { now := t0 s, _ := newService(t, &now, abono.Config{}) if _, err := s.Comp(context.Background(), "", "annual", nil, ""); err == nil { t.Error("Comp with no subject succeeded") } if _, err := s.Comp(context.Background(), "s1", "", nil, ""); err == nil { t.Error("Comp with no plan succeeded") } }