#83: accept boolean public field in create API (false->unlisted, true->public)
This commit is contained in:
@@ -22,6 +22,10 @@ curl -X POST http://localhost:8080/api/pastes \
|
|||||||
|
|
||||||
- `expires_in` is a Go duration string (`90m`, `6h`, `336h`). Omit for no expiry.
|
- `expires_in` is a Go duration string (`90m`, `6h`, `336h`). Omit for no expiry.
|
||||||
- `visibility` is `public` or `unlisted`.
|
- `visibility` is `public` or `unlisted`.
|
||||||
|
- Alternatively (or additionally), `public` may be sent as a boolean (#83):
|
||||||
|
`false` maps to `unlisted` and `true` maps to `public`. When both fields are
|
||||||
|
present, the boolean `public` takes precedence over the string `visibility`.
|
||||||
|
Omitting both defaults to `public`.
|
||||||
- `burn_after_reads` sets how many reads the paste survives (default 1 when
|
- `burn_after_reads` sets how many reads the paste survives (default 1 when
|
||||||
`burn_after_read` is true). A read is counted per unique viewer session;
|
`burn_after_read` is true). A read is counted per unique viewer session;
|
||||||
the same viewer returning within 15 minutes does not count again.
|
the same viewer returning within 15 minutes does not count again.
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
// #83 regression tests: `public` boolean in the create payload must map to
|
||||||
|
// visibility (false -> unlisted, true -> public); string `visibility` still works.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createPasteBody(t *testing.T, h *apiServer, body string) map[string]any {
|
||||||
|
req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(body))
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
h.routes().ServeHTTP(rec, req)
|
||||||
|
if rec.Code != 201 {
|
||||||
|
t.Fatalf("create: got %d: %s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
var resp map[string]any
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||||
|
t.Fatalf("bad json: %v", err)
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVis(t *testing.T, h *apiServer, id string) string {
|
||||||
|
req := httptest.NewRequest("GET", "/api/pastes/"+id, nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
h.routes().ServeHTTP(rec, req)
|
||||||
|
if rec.Code != 200 {
|
||||||
|
t.Fatalf("get %s: got %d", id, rec.Code)
|
||||||
|
}
|
||||||
|
var resp struct {
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||||
|
t.Fatalf("bad json: %v", err)
|
||||||
|
}
|
||||||
|
return resp.Visibility
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPublicBooleanFalseMapsToUnlisted: {"public": false} must create an unlisted paste.
|
||||||
|
func TestPublicBooleanFalseMapsToUnlisted(t *testing.T) {
|
||||||
|
s := testServer(t)
|
||||||
|
resp := createPasteBody(t, s, `{"content":"x","public":false}`)
|
||||||
|
if v := getVis(t, s, resp["id"].(string)); v != "unlisted" {
|
||||||
|
t.Fatalf("public:false -> got visibility %q, want unlisted", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPublicBooleanTrueMapsToPublic: {"public": true} must create a public paste.
|
||||||
|
func TestPublicBooleanTrueMapsToPublic(t *testing.T) {
|
||||||
|
s := testServer(t)
|
||||||
|
resp := createPasteBody(t, s, `{"content":"x","public":true}`)
|
||||||
|
if v := getVis(t, s, resp["id"].(string)); v != "public" {
|
||||||
|
t.Fatalf("public:true -> got visibility %q, want public", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPublicBooleanOverridesString: boolean wins when both fields are sent.
|
||||||
|
func TestPublicBooleanOverridesString(t *testing.T) {
|
||||||
|
s := testServer(t)
|
||||||
|
resp := createPasteBody(t, s, `{"content":"x","visibility":"public","public":false}`)
|
||||||
|
if v := getVis(t, s, resp["id"].(string)); v != "unlisted" {
|
||||||
|
t.Fatalf("boolean override -> got %q, want unlisted", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVisibilityStringStillWorks: existing string contract unchanged.
|
||||||
|
func TestVisibilityStringStillWorks(t *testing.T) {
|
||||||
|
s := testServer(t)
|
||||||
|
resp := createPasteBody(t, s, `{"content":"x","visibility":"unlisted"}`)
|
||||||
|
if v := getVis(t, s, resp["id"].(string)); v != "unlisted" {
|
||||||
|
t.Fatalf("string field -> got %q, want unlisted", v)
|
||||||
|
}
|
||||||
|
resp = createPasteBody(t, s, `{"content":"y","visibility":"public"}`)
|
||||||
|
if v := getVis(t, s, resp["id"].(string)); v != "public" {
|
||||||
|
t.Fatalf("string field -> got %q, want public", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPublicListExcludesPublicFalse: {"public":false} pastes stay out of /api/public.
|
||||||
|
func TestPublicListExcludesPublicFalse(t *testing.T) {
|
||||||
|
s := testServer(t)
|
||||||
|
createPasteBody(t, s, `{"content":"hidden","public":false}`)
|
||||||
|
req := httptest.NewRequest("GET", "/api/public", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
s.routes().ServeHTTP(rec, req)
|
||||||
|
var resp struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||||
|
if resp.Total != 0 {
|
||||||
|
t.Fatalf("public:false paste leaked into /api/public: total=%d", resp.Total)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ type Paste struct {
|
|||||||
BurnAfterRead bool `json:"burn_after_read,omitempty"`
|
BurnAfterRead bool `json:"burn_after_read,omitempty"`
|
||||||
BurnAfterReads *int `json:"burn_after_reads,omitempty"` // #49: readable N times (default 1)
|
BurnAfterReads *int `json:"burn_after_reads,omitempty"` // #49: readable N times (default 1)
|
||||||
Visibility string `json:"visibility"`
|
Visibility string `json:"visibility"`
|
||||||
|
// #83: accept "public": true/false as an alias for visibility.
|
||||||
|
Public *bool `json:"public,omitempty"`
|
||||||
CanID *string `json:"can_id,omitempty"`
|
CanID *string `json:"can_id,omitempty"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
DeletedAt *int64 `json:"deleted_at,omitempty"`
|
DeletedAt *int64 `json:"deleted_at,omitempty"`
|
||||||
@@ -210,6 +212,14 @@ func (s *Store) CreatePaste(p *Paste) (*Paste, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visibility := p.Visibility
|
visibility := p.Visibility
|
||||||
|
// #83: "public": false -> unlisted, true -> public; overrides string field
|
||||||
|
if p.Public != nil {
|
||||||
|
if *p.Public {
|
||||||
|
visibility = "public"
|
||||||
|
} else {
|
||||||
|
visibility = "unlisted"
|
||||||
|
}
|
||||||
|
}
|
||||||
if visibility == "" {
|
if visibility == "" {
|
||||||
visibility = "public"
|
visibility = "public"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user