bookmarks_api_test.go (3778B)
1 package internal 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "net/http" 8 "net/http/cookiejar" 9 "net/http/httptest" 10 "strings" 11 "testing" 12 ) 13 14 func TestBookmarksCRUDAndUnauthorized(t *testing.T) { 15 t.Parallel() 16 page := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 w.Header().Set("Content-Type", "text/html") 18 io.WriteString(w, "<!doctype html><html><head><title>CRUD Title</title></head><body>body</body></html>") 19 })) 20 defer page.Close() 21 22 db, err := OpenAtPath(t.Context(), t.TempDir()+"/crud.db") 23 if err != nil { 24 t.Fatalf("open: %v", err) 25 } 26 t.Cleanup(func() { _ = db.Close() }) 27 h, _ := NewHandler(db) 28 srv := httptest.NewServer(h) 29 defer srv.Close() 30 31 // Unauth GET /bookmarks → 401 32 if resp, _ := http.Get(srv.URL + "/bookmarks"); resp.StatusCode != http.StatusUnauthorized { 33 t.Fatalf("/bookmarks unauth %d", resp.StatusCode) 34 } 35 // Unauth POST /bookmarks → 401 36 rp, _ := http.NewRequest(http.MethodPost, srv.URL+"/bookmarks", strings.NewReader("{}")) 37 rp.Header.Set("Content-Type", "application/json") 38 if rs, _ := http.DefaultClient.Do(rp); rs.StatusCode != http.StatusUnauthorized { 39 t.Fatalf("post unauth %d", rs.StatusCode) 40 } 41 42 // Sign up client 43 jar, _ := cookiejar.New(nil) 44 client := &http.Client{Jar: jar} 45 body, _ := json.Marshal(map[string]string{"username": "crud", "password": "pw"}) 46 rq, _ := http.NewRequest(http.MethodPost, srv.URL+"/signup", bytes.NewReader(body)) 47 rq.Header.Set("Content-Type", "application/json") 48 rsu, _ := client.Do(rq) 49 if rsu.StatusCode != http.StatusOK { 50 t.Fatalf("signup %d", rsu.StatusCode) 51 } 52 rsu.Body.Close() 53 54 // Wrong content type → 415 55 bad, _ := http.NewRequest(http.MethodPost, srv.URL+"/bookmarks", strings.NewReader("not json")) 56 bad.Header.Set("Content-Type", "text/plain") 57 rbad, _ := client.Do(bad) 58 if rbad.StatusCode != http.StatusBadRequest { 59 t.Fatalf("ct status %d", rbad.StatusCode) 60 } 61 rbad.Body.Close() 62 63 // Create 64 create := map[string]any{"url": page.URL} 65 cbody, _ := json.Marshal(create) 66 rc, _ := http.NewRequest(http.MethodPost, srv.URL+"/bookmarks", bytes.NewReader(cbody)) 67 rc.Header.Set("Content-Type", "application/json") 68 rcr, _ := client.Do(rc) 69 if rcr.StatusCode != http.StatusCreated { 70 t.Fatalf("create %d", rcr.StatusCode) 71 } 72 var created map[string]any 73 _ = json.NewDecoder(rcr.Body).Decode(&created) 74 rcr.Body.Close() 75 id := int(created["id"].(float64)) 76 if created["title"].(string) != "CRUD Title" { 77 t.Fatalf("title inferred") 78 } 79 80 // Duplicate POST should 200 and touch updated_at 81 rc2, _ := http.NewRequest(http.MethodPost, srv.URL+"/bookmarks", bytes.NewReader(cbody)) 82 rc2.Header.Set("Content-Type", "application/json") 83 rcr2, _ := client.Do(rc2) 84 if rcr2.StatusCode != http.StatusOK { 85 t.Fatalf("dup %d", rcr2.StatusCode) 86 } 87 rcr2.Body.Close() 88 89 // GET list and RSS 90 if _, err := client.Get(srv.URL + "/bookmarks"); err != nil { 91 t.Fatalf("list err: %v", err) 92 } 93 if resp, _ := client.Get(srv.URL + "/bookmarks.xml"); resp.StatusCode != 200 { 94 t.Fatalf("rss %d", resp.StatusCode) 95 } 96 97 // PUT update (preserve description when omitted) 98 put := map[string]any{"url": page.URL, "title": "Changed"} 99 pb, _ := json.Marshal(put) 100 pr, _ := http.NewRequest(http.MethodPut, srv.URL+"/bookmarks/"+strconvItoa(id), bytes.NewReader(pb)) 101 pr.Header.Set("Content-Type", "application/json") 102 prr, _ := client.Do(pr) 103 if prr.StatusCode != 200 { 104 t.Fatalf("put %d", prr.StatusCode) 105 } 106 var upd map[string]any 107 _ = json.NewDecoder(prr.Body).Decode(&upd) 108 prr.Body.Close() 109 if upd["title"].(string) != "Changed" { 110 t.Fatalf("put title") 111 } 112 113 // DELETE 114 dr, _ := http.NewRequest(http.MethodDelete, srv.URL+"/bookmarks/"+strconvItoa(id), nil) 115 drr, _ := client.Do(dr) 116 if drr.StatusCode != 204 { 117 t.Fatalf("delete %d", drr.StatusCode) 118 } 119 }