marky

Lightweight bookmarking + plaintext archive
Log | Files | Refs | README | LICENSE

more_api_tests.go (4416B)


      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 	"strconv"
     11 	"testing"
     12 )
     13 
     14 func TestPublicUserListingJSON(t *testing.T) {
     15 	t.Parallel()
     16 	db, _ := OpenAtPath(t.Context(), t.TempDir()+"/u.db")
     17 	t.Cleanup(func() { _ = db.Close() })
     18 	h, _ := NewHandler(db)
     19 	srv := httptest.NewServer(h)
     20 	defer srv.Close()
     21 
     22 	// Unknown user
     23 	if resp, _ := http.Get(srv.URL + "/u/nouser"); resp.StatusCode != http.StatusNotFound {
     24 		t.Fatalf("/u/nouser expected 404, got %d", resp.StatusCode)
     25 	}
     26 
     27 	// Create user and ensure empty list
     28 	_, _ = db.CreateUser(t.Context(), "eve", "pw")
     29 	if resp, _ := http.Get(srv.URL + "/u/eve"); resp.StatusCode != 200 {
     30 		t.Fatalf("/u/eve %d", resp.StatusCode)
     31 	}
     32 }
     33 
     34 func TestBookmarksParamValidationAndRSSAccept(t *testing.T) {
     35 	t.Parallel()
     36 	page := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     37 		w.Header().Set("Content-Type", "text/html")
     38 		io.WriteString(w, "<!doctype html><html><head><title>P</title></head><body/></html>")
     39 	}))
     40 	defer page.Close()
     41 
     42 	db, _ := OpenAtPath(t.Context(), t.TempDir()+"/p.db")
     43 	t.Cleanup(func() { _ = db.Close() })
     44 	h, _ := NewHandler(db)
     45 	srv := httptest.NewServer(h)
     46 	defer srv.Close()
     47 
     48 	jar, _ := cookiejar.New(nil)
     49 	client := &http.Client{Jar: jar}
     50 	// signup
     51 	body, _ := json.Marshal(map[string]string{"username": "param", "password": "pw"})
     52 	req, _ := http.NewRequest(http.MethodPost, srv.URL+"/signup", bytes.NewReader(body))
     53 	req.Header.Set("Content-Type", "application/json")
     54 	rs, _ := client.Do(req)
     55 	if rs.StatusCode != 200 {
     56 		t.Fatalf("signup %d", rs.StatusCode)
     57 	}
     58 	rs.Body.Close()
     59 	// One bookmark
     60 	b2, _ := json.Marshal(map[string]any{"url": page.URL})
     61 	rq, _ := http.NewRequest(http.MethodPost, srv.URL+"/bookmarks", bytes.NewReader(b2))
     62 	rq.Header.Set("Content-Type", "application/json")
     63 	cr, _ := client.Do(rq)
     64 	if cr.StatusCode != 201 {
     65 		t.Fatalf("create %d", cr.StatusCode)
     66 	}
     67 	cr.Body.Close()
     68 
     69 	// invalid limit
     70 	if resp, _ := client.Get(srv.URL + "/bookmarks?limit=abc"); resp.StatusCode != http.StatusBadRequest {
     71 		t.Fatalf("limit status %d", resp.StatusCode)
     72 	}
     73 	// invalid offset
     74 	if resp, _ := client.Get(srv.URL + "/bookmarks?offset=-1"); resp.StatusCode != http.StatusBadRequest {
     75 		t.Fatalf("offset status %d", resp.StatusCode)
     76 	}
     77 	// Accept RSS
     78 	req2, _ := http.NewRequest(http.MethodGet, srv.URL+"/bookmarks", nil)
     79 	req2.Header.Set("Accept", "application/rss+xml")
     80 	rs2, _ := client.Do(req2)
     81 	if rs2.StatusCode != 200 {
     82 		t.Fatalf("accept rss %d", rs2.StatusCode)
     83 	}
     84 	rs2.Body.Close()
     85 }
     86 
     87 func TestArchiveFetchOwnerOnly(t *testing.T) {
     88 	t.Parallel()
     89 	// Page to fetch
     90 	page := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     91 		w.Header().Set("Content-Type", "text/html")
     92 		io.WriteString(w, "<!doctype html><html><head><title>A</title></head><body/></html>")
     93 	}))
     94 	defer page.Close()
     95 
     96 	db, _ := OpenAtPath(t.Context(), t.TempDir()+"/arc.db")
     97 	t.Cleanup(func() { _ = db.Close() })
     98 	h, _ := NewHandler(db)
     99 	srv := httptest.NewServer(h)
    100 	defer srv.Close()
    101 
    102 	// signup owner
    103 	j1, _ := cookiejar.New(nil)
    104 	c1 := &http.Client{Jar: j1}
    105 	s1, _ := json.Marshal(map[string]string{"username": "own", "password": "pw"})
    106 	rq1, _ := http.NewRequest(http.MethodPost, srv.URL+"/signup", bytes.NewReader(s1))
    107 	rq1.Header.Set("Content-Type", "application/json")
    108 	_, _ = c1.Do(rq1)
    109 	// create via DB without archive
    110 	u, _ := db.GetUserByUsername(t.Context(), "own")
    111 	bmk, err := db.CreateBookmark(t.Context(), u.ID, page.URL+"/priv", "PVT", nil)
    112 	if err != nil {
    113 		t.Fatalf("create raw: %v", err)
    114 	}
    115 	// owner can fetch with ?fetch=1
    116 	resp, _ := c1.Get(srv.URL + "/bookmarks/" + strconv.Itoa(int(bmk.ID)) + "/archive?fetch=1")
    117 	if resp.StatusCode != 200 {
    118 		t.Fatalf("owner fetch %d", resp.StatusCode)
    119 	}
    120 	resp.Body.Close()
    121 
    122 	// signup other user
    123 	j2, _ := cookiejar.New(nil)
    124 	c2 := &http.Client{Jar: j2}
    125 	s2, _ := json.Marshal(map[string]string{"username": "other", "password": "pw"})
    126 	rq2, _ := http.NewRequest(http.MethodPost, srv.URL+"/signup", bytes.NewReader(s2))
    127 	rq2.Header.Set("Content-Type", "application/json")
    128 	_, _ = c2.Do(rq2)
    129 	// other user cannot see private archive
    130 	resp2, _ := c2.Get(srv.URL + "/bookmarks/" + strconv.Itoa(int(bmk.ID)) + "/archive")
    131 	if resp2.StatusCode != 404 {
    132 		t.Fatalf("other archive %d", resp2.StatusCode)
    133 	}
    134 	resp2.Body.Close()
    135 }
    136 
    137 // no helper needed; use strconv.Itoa inline