package handler import ( "context" "encoding/json" "net/http" "net/http/httptest" "reflect" "sort" "strings" "sync" "testing" "5803025048/W04/internal/middleware" "5803025048/W04/internal/models" "5803025048/W04/internal/store" ) // ---------- fake stores (in-memory, buatan tangan) ---------- type fakeUserStore struct { mu sync.Mutex users map[string]*models.User // key: username createErr error getErr error } func newFakeUserStore() *fakeUserStore { return &fakeUserStore{users: map[string]*models.User{}} } func (f *fakeUserStore) Create(_ context.Context, user *models.User) error { f.mu.Lock() defer f.mu.Unlock() if f.createErr != nil { return f.createErr } if _, exists := f.users[user.Username]; exists { return store.ErrUsernameTaken } cp := *user f.users[user.Username] = &cp return nil } func (f *fakeUserStore) GetByUsername(_ context.Context, username string) (*models.User, error) { f.mu.Lock() defer f.mu.Unlock() if f.getErr != nil { return nil, f.getErr } u, ok := f.users[username] if !ok { return nil, store.ErrNotFound } cp := *u return &cp, nil } type fakeSessionStore struct { mu sync.Mutex sessions map[string]*models.Session // key: token createErr error getErr error deleteErr error } func newFakeSessionStore() *fakeSessionStore { return &fakeSessionStore{sessions: map[string]*models.Session{}} } func (f *fakeSessionStore) Create(_ context.Context, session *models.Session) error { f.mu.Lock() defer f.mu.Unlock() if f.createErr != nil { return f.createErr } cp := *session f.sessions[session.Token] = &cp return nil } func (f *fakeSessionStore) Get(_ context.Context, token string) (*models.Session, error) { f.mu.Lock() defer f.mu.Unlock() if f.getErr != nil { return nil, f.getErr } s, ok := f.sessions[token] if !ok { return nil, store.ErrNotFound } cp := *s return &cp, nil } func (f *fakeSessionStore) Delete(_ context.Context, token string) error { f.mu.Lock() defer f.mu.Unlock() if f.deleteErr != nil { return f.deleteErr } delete(f.sessions, token) return nil } func (f *fakeSessionStore) has(token string) bool { f.mu.Lock() defer f.mu.Unlock() _, ok := f.sessions[token] return ok } type fakeSnippetStore struct { mu sync.Mutex snippets map[string]*models.Snippet // key: id createErr error getErr error listErr error deleteErr error } func newFakeSnippetStore() *fakeSnippetStore { return &fakeSnippetStore{snippets: map[string]*models.Snippet{}} } func (f *fakeSnippetStore) seed(id, title, content, userID string) { f.mu.Lock() defer f.mu.Unlock() f.snippets[id] = &models.Snippet{ID: id, Title: title, Content: content, UserID: userID} } func (f *fakeSnippetStore) Create(_ context.Context, snippet *models.Snippet) error { f.mu.Lock() defer f.mu.Unlock() if f.createErr != nil { return f.createErr } cp := *snippet f.snippets[snippet.ID] = &cp return nil } func (f *fakeSnippetStore) GetByID(_ context.Context, id string) (*models.Snippet, error) { f.mu.Lock() defer f.mu.Unlock() if f.getErr != nil { return nil, f.getErr } s, ok := f.snippets[id] if !ok { return nil, store.ErrNotFound } cp := *s return &cp, nil } // GetByUserID sengaja mengembalikan nil (bukan slice kosong) jika tidak ada hasil, // supaya handler terbukti menghasilkan "[]" dan bukan "null". func (f *fakeSnippetStore) GetByUserID(_ context.Context, userID string) ([]*models.Snippet, error) { f.mu.Lock() defer f.mu.Unlock() if f.listErr != nil { return nil, f.listErr } var out []*models.Snippet for _, s := range f.snippets { if s.UserID == userID { cp := *s out = append(out, &cp) } } return out, nil } func (f *fakeSnippetStore) Delete(_ context.Context, id string) error { f.mu.Lock() defer f.mu.Unlock() if f.deleteErr != nil { return f.deleteErr } if _, ok := f.snippets[id]; !ok { return store.ErrNotFound } delete(f.snippets, id) return nil } func (f *fakeSnippetStore) has(id string) bool { f.mu.Lock() defer f.mu.Unlock() _, ok := f.snippets[id] return ok } // ---------- helper tes ---------- func newJSONRequest(method, target, body string) *http.Request { req := httptest.NewRequest(method, target, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") return req } func withUser(req *http.Request, userID string) *http.Request { return req.WithContext(context.WithValue(req.Context(), middleware.UserIDKey, userID)) } func assertJSONContentType(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/json") { t.Fatalf("Content-Type = %q, want application/json", ct) } } func decodeMap(t *testing.T, raw []byte) map[string]any { t.Helper() var m map[string]any if err := json.Unmarshal(raw, &m); err != nil { t.Fatalf("body is not a JSON object: %v (body=%q)", err, string(raw)) } return m } func assertKeys(t *testing.T, m map[string]any, want ...string) { t.Helper() got := make([]string, 0, len(m)) for k := range m { got = append(got, k) } sort.Strings(got) sort.Strings(want) if !reflect.DeepEqual(got, want) { t.Fatalf("keys = %v, want exactly %v", got, want) } } func errNotFoundForTest() error { return store.ErrNotFound }