| 1 | package abono_test |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "testing" |
| 7 | "time" |
| 8 | |
| 9 | "gorm.io/gorm" |
| 10 | |
| 11 | "amadan.net/rastrillo/abono" |
| 12 | ) |
| 13 | |
| 14 | func TestCompAndEndCompCallOnChangeInTheSameTransaction(t *testing.T) { |
| 15 | now := t0 |
| 16 | var calls []string |
| 17 | var seen int64 = -1 |
| 18 | var failWith error |
| 19 | s, db := newService(t, &now, abono.Config{ |
| 20 | OnChange: func(ctx context.Context, tx *gorm.DB, subject string) error { |
| 21 | calls = append(calls, subject) |
| 22 | // Same transaction: the comp written a moment ago is visible |
| 23 | // here, though it is not committed yet. |
| 24 | if err := tx.Table("abono_comps").Where("subject = ? AND status = 'active'", subject).Count(&seen).Error; err != nil { |
| 25 | return err |
| 26 | } |
| 27 | return failWith |
| 28 | }, |
| 29 | }) |
| 30 | ctx := context.Background() |
| 31 | |
| 32 | id, err := s.Comp(ctx, "s1", "annual", nil, "for the editor") |
| 33 | if err != nil { |
| 34 | t.Fatalf("Comp: %v", err) |
| 35 | } |
| 36 | if id <= 0 { |
| 37 | t.Errorf("Comp id = %d", id) |
| 38 | } |
| 39 | if len(calls) != 1 || calls[0] != "s1" || seen != 1 { |
| 40 | t.Errorf("OnChange calls = %v, active comps seen inside = %d; want [s1] and 1", calls, seen) |
| 41 | } |
| 42 | if got := db.dump("SELECT subject, plan, status, ends_at, note FROM abono_comps"); got != "s1|annual|active||for the editor\n" { |
| 43 | t.Errorf("comps = %q", got) |
| 44 | } |
| 45 | |
| 46 | // A hook error rolls Comp back. |
| 47 | failWith = errors.New("hook failed") |
| 48 | if _, err := s.Comp(ctx, "s2", "annual", nil, ""); !errors.Is(err, failWith) { |
| 49 | t.Errorf("Comp with a failing hook: %v, want the hook's error", err) |
| 50 | } |
| 51 | if got := db.dump("SELECT subject FROM abono_comps WHERE subject = 's2'"); got != "" { |
| 52 | t.Errorf("a comp survived its hook's failure: %q", got) |
| 53 | } |
| 54 | |
| 55 | // And rolls EndComp back. |
| 56 | if err := s.EndComp(ctx, id); !errors.Is(err, failWith) { |
| 57 | t.Errorf("EndComp with a failing hook: %v, want the hook's error", err) |
| 58 | } |
| 59 | if got := db.dump("SELECT status FROM abono_comps WHERE id = ?", id); got != "active\n" { |
| 60 | t.Errorf("status after a rolled-back EndComp = %q", got) |
| 61 | } |
| 62 | |
| 63 | failWith = nil |
| 64 | calls = nil |
| 65 | now = t0.Add(time.Hour) |
| 66 | if err := s.EndComp(ctx, id); err != nil { |
| 67 | t.Fatalf("EndComp: %v", err) |
| 68 | } |
| 69 | if len(calls) != 1 || calls[0] != "s1" { |
| 70 | t.Errorf("OnChange calls after EndComp = %v", calls) |
| 71 | } |
| 72 | if got := db.dump("SELECT status FROM abono_comps WHERE id = ?", id); got != "canceled\n" { |
| 73 | t.Errorf("status = %q", got) |
| 74 | } |
| 75 | if has, err := s.HasPlan(ctx, "s1", "annual"); err != nil || has { |
| 76 | t.Errorf("HasPlan after EndComp = %v, %v", has, err) |
| 77 | } |
| 78 | |
| 79 | // Ending it again changes nothing, so tells nobody. |
| 80 | calls = nil |
| 81 | if err := s.EndComp(ctx, id); err != nil { |
| 82 | t.Errorf("EndComp again: %v", err) |
| 83 | } |
| 84 | if len(calls) != 0 { |
| 85 | t.Errorf("EndComp on an ended comp called OnChange: %v", calls) |
| 86 | } |
| 87 | if err := s.EndComp(ctx, id+100); !errors.Is(err, abono.ErrNotFound) { |
| 88 | t.Errorf("EndComp of a missing comp: %v, want ErrNotFound", err) |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | func TestCompRefusesNoSubjectOrPlan(t *testing.T) { |
| 93 | now := t0 |
| 94 | s, _ := newService(t, &now, abono.Config{}) |
| 95 | if _, err := s.Comp(context.Background(), "", "annual", nil, ""); err == nil { |
| 96 | t.Error("Comp with no subject succeeded") |
| 97 | } |
| 98 | if _, err := s.Comp(context.Background(), "s1", "", nil, ""); err == nil { |
| 99 | t.Error("Comp with no plan succeeded") |
| 100 | } |
| 101 | } |
| 102 | |