package api import ( "bytes" "net/http" "net/http/httptest" "testing" "gardomatic.kleiax.de/internal/storage" "github.com/julienschmidt/httprouter" ) type journalTestModel struct { items map[int]storage.JournalEntry nextID int lastListType storage.JournalEntryType } func (m *journalTestModel) Insert(entry storage.JournalEntry) (storage.JournalEntry, error) { m.nextID++ entry.ID, entry.Version = m.nextID, 1 m.items[entry.ID] = entry return entry, nil } func (m *journalTestModel) Get(gardenID, id int) (storage.JournalEntry, error) { entry, ok := m.items[id] if !ok || entry.GardenID != gardenID { return storage.JournalEntry{}, storage.ErrRecordNotFound } return entry, nil } func (m *journalTestModel) GetAllForGarden(gardenID int, entryType storage.JournalEntryType) ([]storage.JournalEntry, error) { m.lastListType = entryType entries := []storage.JournalEntry{} for _, entry := range m.items { if entry.GardenID == gardenID && entry.EntryType == entryType { entries = append(entries, entry) } } return entries, nil } func (m *journalTestModel) Update(gardenID int, entry storage.JournalEntry) (storage.JournalEntry, error) { if _, err := m.Get(gardenID, entry.ID); err != nil { return storage.JournalEntry{}, err } entry.Version++ m.items[entry.ID] = entry return entry, nil } func (m *journalTestModel) Delete(gardenID, id int) error { if _, err := m.Get(gardenID, id); err != nil { return err } delete(m.items, id) return nil } func (m *journalTestModel) InsertAttachment(int, int, storage.JournalAttachment) (storage.JournalAttachment, error) { return storage.JournalAttachment{}, nil } func (m *journalTestModel) GetAttachment(int, int, int) (storage.JournalAttachment, error) { return storage.JournalAttachment{}, storage.ErrRecordNotFound } func (m *journalTestModel) DeleteAttachment(int, int, int) error { return nil } type journalTagTestModel struct{} func (journalTagTestModel) Get(int, storage.TagEntity, int) ([]string, error) { return nil, nil } func (journalTagTestModel) Set(_ int, _ storage.TagEntity, _ int, tags []string) ([]string, error) { return tags, nil } func (journalTagTestModel) GetAllForGarden(int) ([]string, error) { return nil, nil } func serveJournalRequest(app *application, user storage.User, method, path string, body []byte) *httptest.ResponseRecorder { router := httprouter.New() protect := func(handler http.HandlerFunc) http.HandlerFunc { return app.requireActivatedUser(app.requireGardenMember(handler)) } router.HandlerFunc(http.MethodPost, "/v1/gardens/:gardenID/journal", protect(app.createJournalEntryHandler)) router.HandlerFunc(http.MethodGet, "/v1/gardens/:gardenID/journal", protect(app.listJournalEntriesHandler)) request := httptest.NewRequest(method, path, bytes.NewReader(body)) request.Header.Set("Content-Type", "application/json") response := httptest.NewRecorder() router.ServeHTTP(response, app.contextSetAuthenticatedUser(request, user)) return response } func TestJournalAPISeparatesPinboardEntries(t *testing.T) { app, _, members := newGardenTestApplication() user := storage.User{ID: 12, Name: "Ada", Activated: true} members.members[[2]int{3, user.ID}] = storage.GardenMember{GardenID: 3, UserID: user.ID, Role: storage.GardenRoleMember} model := &journalTestModel{items: map[int]storage.JournalEntry{ 1: {ID: 1, GardenID: 3, EntryType: storage.JournalEntryTypeJournal, Title: "Ernte"}, 2: {ID: 2, GardenID: 3, EntryType: storage.JournalEntryTypePinboard, Title: "Sitzecke"}, }, nextID: 2} app.models.Journal = model app.models.Tags = journalTagTestModel{} listed := serveJournalRequest(app, user, http.MethodGet, "/v1/gardens/3/journal?type=pinboard", nil) if listed.Code != http.StatusOK || model.lastListType != storage.JournalEntryTypePinboard { t.Fatalf("list pinboard: status=%d type=%q body=%s", listed.Code, model.lastListType, listed.Body.String()) } if body := listed.Body.String(); !bytes.Contains([]byte(body), []byte("Sitzecke")) || bytes.Contains([]byte(body), []byte("Ernte")) { t.Fatalf("list mixes entry types: %s", body) } created := serveJournalRequest(app, user, http.MethodPost, "/v1/gardens/3/journal", []byte(`{"title":"Teichidee","entry_type":"pinboard"}`)) if created.Code != http.StatusCreated || model.items[3].EntryType != storage.JournalEntryTypePinboard { t.Fatalf("create pinboard: status=%d entry=%+v body=%s", created.Code, model.items[3], created.Body.String()) } imageOnly := serveJournalRequest(app, user, http.MethodPost, "/v1/gardens/3/journal", []byte(`{"entry_type":"pinboard"}`)) if imageOnly.Code != http.StatusCreated || model.items[4].Title != "" { t.Fatalf("create titleless pinboard entry: status=%d entry=%+v body=%s", imageOnly.Code, model.items[4], imageOnly.Body.String()) } untitledJournal := serveJournalRequest(app, user, http.MethodPost, "/v1/gardens/3/journal", []byte(`{"entry_type":"journal"}`)) if untitledJournal.Code != http.StatusUnprocessableEntity { t.Fatalf("untitled journal status: got %d, want %d", untitledJournal.Code, http.StatusUnprocessableEntity) } invalid := serveJournalRequest(app, user, http.MethodGet, "/v1/gardens/3/journal?type=unknown", nil) if invalid.Code != http.StatusBadRequest { t.Fatalf("invalid type: got %d, want %d", invalid.Code, http.StatusBadRequest) } }