package store import ( "context" "testing" ) // Chapter 23.4 disables a hook after twenty failures in a row, and "in a row" is the whole rule. func TestAHookStopsAfterTwentyFailuresInARow(t *testing.T) { ctx := context.Background() db := open(t) const url = "https://example.com/hook" for i := range HookFailureLimit - 1 { if err := db.HookFailed(ctx, "john/johnbot", url, "500 Internal Server Error"); err != nil { t.Fatal(err) } state, err := db.HooksOf(ctx, "john/johnbot") if err != nil { t.Fatal(err) } if state[url].Disabled { t.Fatalf("the hook stopped after %d failures, before the limit of %d", i+1, HookFailureLimit) } } if err := db.HookFailed(ctx, "john/johnbot", url, "500 Internal Server Error"); err != nil { t.Fatal(err) } state, err := db.HooksOf(ctx, "john/johnbot") if err != nil { t.Fatal(err) } if !state[url].Disabled { t.Errorf("the hook failed %d times and is still enabled", HookFailureLimit) } if state[url].LastError == "" { t.Error("the config page has nothing to show about why it stopped") } } // One delivery clears the count, or a hook that fails once a week eventually stops for no reason. func TestOneDeliveryClearsTheFailureCount(t *testing.T) { ctx := context.Background() db := open(t) const url = "https://example.com/hook" for range HookFailureLimit - 1 { if err := db.HookFailed(ctx, "john/johnbot", url, "timeout"); err != nil { t.Fatal(err) } } if err := db.HookDelivered(ctx, "john/johnbot", url); err != nil { t.Fatal(err) } state, err := db.HooksOf(ctx, "john/johnbot") if err != nil { t.Fatal(err) } if state[url].Failures != 0 { t.Errorf("a delivered hook still counts %d failures", state[url].Failures) } if state[url].LastError != "" { t.Errorf("a delivered hook still shows the error %q", state[url].LastError) } if state[url].LastAt.IsZero() { t.Error("a delivered hook has no delivery time to show") } } // The cursor is what stops a restart from sending every event in the table again. func TestTheCursorNeitherRepeatsNorSkips(t *testing.T) { ctx := context.Background() db := open(t) for i := range 3 { if err := db.Record(ctx, Event{Kind: Pushed, Actor: "john", Repo: "john/johnbot", Ref: "refs/heads/master", Title: string(rune('a' + i))}); err != nil { t.Fatal(err) } } // A first start sends no backlog, because a new hook is not a request for history. if err := db.StartWebhooksHere(ctx); err != nil { t.Fatal(err) } at, err := db.WebhookCursor(ctx) if err != nil { t.Fatal(err) } pending, err := db.EventsAfter(ctx, at, 100) if err != nil { t.Fatal(err) } if len(pending) != 0 { t.Fatalf("a first start would have sent %d old events", len(pending)) } if err := db.Record(ctx, Event{Kind: ThreadOpened, Actor: "lisa", Repo: "john/johnbot", Number: 4, Title: "a new one"}); err != nil { t.Fatal(err) } pending, err = db.EventsAfter(ctx, at, 100) if err != nil { t.Fatal(err) } if len(pending) != 1 || pending[0].Title != "a new one" { t.Fatalf("the event after the cursor is %v, wanted the one that just happened", pending) } // A second start keeps the cursor it has, or every restart replays the same events. if err := db.SetWebhookCursor(ctx, pending[0].ID); err != nil { t.Fatal(err) } if err := db.StartWebhooksHere(ctx); err != nil { t.Fatal(err) } again, err := db.WebhookCursor(ctx) if err != nil { t.Fatal(err) } if again != pending[0].ID { t.Errorf("a restart moved the cursor from %d to %d", pending[0].ID, again) } }