package service import ( "context" "errors" "testing" "github.com/prabhupadasays/spsays-bff/internal/repository" "go.uber.org/zap" ) // Categories are managed from the admin: a new one is offered on the form // and accepts feedback; renaming relabels past feedback; archiving stops new // submissions but keeps the label; in-use categories can't be deleted. func TestFeedbackCategories(t *testing.T) { _, _, pg := testChatService(t) ctx := context.Background() repo := repository.NewFeedbackCategoryRepo(pg) cats := NewFeedbackCategoryService(repo) fb := NewFeedbackService(repository.NewFeedbackRepo(pg), repo, nil, nil, zap.NewNop()) user := chatUser(t, pg, "Feedback") t.Cleanup(func() { bg := context.Background() _, _ = pg.Pool.Exec(bg, `DELETE FROM feedback WHERE user_id = $1`, user) _, _ = pg.Pool.Exec(bg, `DELETE FROM feedback_categories WHERE key LIKE 'zz_qa_audio%'`) }) // The seeded categories production already uses are all on the form. active, err := cats.Active(ctx) if err != nil { t.Fatal(err) } seen := map[string]bool{} for _, c := range active { seen[c.Key] = true } for _, k := range []string{"general", "bug", "feature", "other"} { if !seen[k] { t.Errorf("seeded category %q missing from the form", k) } } // Keys come from the name, and never collide. c1, err := cats.Create(ctx, "ZZ QA Audio problem", "Playback trouble") if err != nil { t.Fatal(err) } c2, err := cats.Create(ctx, "ZZ QA audio problem!", "") if err != nil { t.Fatal(err) } if c1.Key != "zz_qa_audio_problem" || c2.Key != "zz_qa_audio_problem_2" { t.Errorf("keys = %q, %q", c1.Key, c2.Key) } if _, err := cats.Create(ctx, " ", ""); !errors.Is(err, ErrCategoryLabel) { t.Errorf("blank name: %v, want ErrCategoryLabel", err) } // New categories take feedback; unknown ones don't. if _, err := fb.Submit(ctx, &user, c1.Key, "the audio stutters", nil); err != nil { t.Fatalf("submit in a new category: %v", err) } if _, err := fb.Submit(ctx, &user, "no_such_category", "x", nil); !errors.Is(err, ErrFeedbackCategory) { t.Errorf("unknown category: %v, want ErrFeedbackCategory", err) } // Renaming relabels what's already been filed. if err := cats.Update(ctx, c1.Key, "ZZ QA Audio & playback", "Playback trouble", false); err != nil { t.Fatal(err) } mine, _ := fb.ListForUser(ctx, user, 10) if len(mine) != 1 || mine[0].CategoryLabel != "ZZ QA Audio & playback" || mine[0].Category != c1.Key { t.Errorf("past feedback after rename = %+v", mine) } // Archived: off the form, refuses new feedback, keeps its label. if err := cats.Update(ctx, c1.Key, "ZZ QA Audio & playback", "", true); err != nil { t.Fatal(err) } if _, err := fb.Submit(ctx, &user, c1.Key, "again", nil); !errors.Is(err, ErrFeedbackCategory) { t.Errorf("submit to an archived category: %v, want ErrFeedbackCategory", err) } active, _ = cats.Active(ctx) for _, c := range active { if c.Key == c1.Key { t.Error("archived category still on the form") } } if mine, _ := fb.ListForUser(ctx, user, 10); mine[0].CategoryLabel != "ZZ QA Audio & playback" { t.Error("archiving lost the label on past feedback") } all, _ := cats.All(ctx) for _, c := range all { if c.Key == c1.Key && (c.ArchivedAt == nil || c.UsageCount != 1) { t.Errorf("admin view of archived category = %+v", c) } } // In use → can't be deleted; unused → can. if err := cats.Delete(ctx, c1.Key); !errors.Is(err, repository.ErrFeedbackCategoryInUse) { t.Errorf("deleting an in-use category: %v, want ErrFeedbackCategoryInUse", err) } if err := cats.Delete(ctx, c2.Key); err != nil { t.Errorf("deleting an unused category: %v", err) } }