Compare commits
9 Commits
f7b7b2d1e6
...
de300a6413
Author | SHA1 | Date |
---|---|---|
Maxim Lebedev | de300a6413 | |
Maxim Lebedev | e822d7273f | |
Maxim Lebedev | 128e596b96 | |
Maxim Lebedev | 3330dda643 | |
Maxim Lebedev | 0ebbd4fb78 | |
Maxim Lebedev | 60aa1d34c1 | |
Maxim Lebedev | 9240cd15cb | |
Maxim Lebedev | 4ca6dc7f9f | |
Maxim Lebedev | 9c2cde03df |
|
@ -6,7 +6,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -127,7 +126,7 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
Action struct {
|
Action struct {
|
||||||
Value domain.Action `json:"-"`
|
domain.Action `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferHTML struct {
|
bufferHTML struct {
|
||||||
|
@ -235,7 +234,7 @@ func (h *Handler) handleCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
content, err := ioutil.ReadAll(file)
|
content, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
|
||||||
|
@ -266,17 +265,11 @@ func (h *Handler) handleCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(common.HeaderLocation, out["self"].String())
|
w.Header().Set(common.HeaderLocation, out.URL.String())
|
||||||
|
|
||||||
if len(out)-1 <= 0 {
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
links := make([]string, 0)
|
links := make([]string, 0)
|
||||||
for rel, value := range out {
|
for i := range out.Syndications {
|
||||||
links = append(links, `<`+value.String()+`>; rel="`+rel+`"`)
|
links = append(links, `<`+out.Syndications[i].String()+`>; rel="syndication"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(common.HeaderLink, strings.Join(links, ", "))
|
w.Header().Set(common.HeaderLink, strings.Join(links, ", "))
|
||||||
|
@ -967,15 +960,15 @@ func (a *Action) UnmarshalJSON(b []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Value = out
|
a.Action = out
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Action) MarshalJSON() ([]byte, error) {
|
func (a Action) MarshalJSON() ([]byte, error) {
|
||||||
if a.Value == domain.ActionUnd {
|
if a.Action == domain.ActionUnd {
|
||||||
return []byte(`""`), nil
|
return []byte(`""`), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return []byte(strconv.Quote(a.Value.String())), nil
|
return []byte(strconv.Quote(a.Action.String())), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package http_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -9,19 +12,105 @@ import (
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/pub/internal/entry/delivery/http"
|
"source.toby3d.me/toby3d/pub/internal/common"
|
||||||
|
"source.toby3d.me/toby3d/pub/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/pub/internal/entry"
|
||||||
|
delivery "source.toby3d.me/toby3d/pub/internal/entry/delivery/http"
|
||||||
|
"source.toby3d.me/toby3d/pub/internal/media"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testRequest struct {
|
type testRequest struct {
|
||||||
Delete *http.Delete `json:"delete,omitempty"`
|
Delete *delivery.Delete `json:"delete,omitempty"`
|
||||||
Content []http.Content `json:"content,omitempty"`
|
Content []delivery.Content `json:"content,omitempty"`
|
||||||
Photo []*http.Figure `json:"photo,omitempty"`
|
Photo []*delivery.Figure `json:"photo,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandler_Create(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("form", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, input := range map[string]url.Values{
|
||||||
|
"simple": {
|
||||||
|
"h": []string{"entry"},
|
||||||
|
"content": []string{"Micropub test of creating a basic h-entry"},
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"h": []string{"entry"},
|
||||||
|
"content": []string{"Micropub test of creating an h-entry with categories. " +
|
||||||
|
"This post should have two categories, test1 and test2"},
|
||||||
|
"category[]": []string{"test1", "test2"},
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"h": []string{"entry"},
|
||||||
|
"content": []string{"Micropub test of creating an h-entry with one category. " +
|
||||||
|
"This post should have one category, test1"},
|
||||||
|
"category": []string{"test1"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
name, input := name, input
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
doCreateRequest(t, strings.NewReader(input.Encode()),
|
||||||
|
common.MIMEApplicationFormCharsetUTF8)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("json", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, input := range map[string]string{
|
||||||
|
"simple": `{"type": ["h-entry"], "properties": {"content": ["Micropub test of creating an h-entry with a JSON request"]}}`,
|
||||||
|
"categories": `{"type": ["h-entry"], "properties": {"content": ["Micropub test of creating an h-entry with a JSON request containing multiple categories. This post should have two categories, test1 and test2."], "category": ["test1", "test2"]}}`,
|
||||||
|
"html": `{"type": ["h-entry"], "properties": {"content": [{"html": "<p>This post has <b>bold</b> and <i>italic</i> text.</p>"}]}}`,
|
||||||
|
"photo": `{"type": ["h-entry"], "properties": {"content": ["Micropub test of creating a photo referenced by URL. This post should include a photo of a sunset."], "photo": ["https://micropub.rocks/media/sunset.jpg"]}}`,
|
||||||
|
"object": `{"type": ["h-entry"], "properties": {"published": ["2017-05-31T12:03:36-07:00"], "content": ["Lunch meeting"], "checkin": [{"type": ["h-card"], "properties": {"name": ["Los Gorditos"], "url": ["https://foursquare.com/v/502c4bbde4b06e61e06d1ebf"], "latitude": [45.524330801154], "longitude": [-122.68068808051], "street-address": ["922 NW Davis St"], "locality": ["Portland"], "region": ["OR"], "country-name": ["United States"], "postal-code": ["97209"]}}]}}`,
|
||||||
|
"photo-alt": `{"type": ["h-entry"], "properties": {"content": ["Micropub test of creating a photo referenced by URL with alt text. This post should include a photo of a sunset."], "photo": [{"value": "https://micropub.rocks/media/sunset.jpg", "alt": "Photo of a sunset"}]}}`,
|
||||||
|
"photos": `{"type": ["h-entry"], "properties": {"content": ["Micropub test of creating multiple photos referenced by URL. This post should include a photo of a city at night."], "photo": ["https://micropub.rocks/media/sunset.jpg", "https://micropub.rocks/media/city-at-night.jpg"]}}`,
|
||||||
|
} {
|
||||||
|
name, input := name, input
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
doCreateRequest(t, strings.NewReader(input), common.MIMEApplicationJSONCharsetUTF8)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO(toby3d): multipart requests
|
||||||
|
}
|
||||||
|
|
||||||
|
func doCreateRequest(tb testing.TB, r io.Reader, contentType string) {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "https://example.com/", r)
|
||||||
|
req.Header.Set(common.HeaderContentType, contentType)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
delivery.NewHandler(entry.NewStubUseCase(nil, domain.TestEntry(tb), true),
|
||||||
|
media.NewDummyUseCase()).ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted {
|
||||||
|
tb.Errorf("%s %s = %d, expect %d or %d", req.Method, req.RequestURI, resp.StatusCode,
|
||||||
|
http.StatusCreated, http.StatusAccepted)
|
||||||
|
}
|
||||||
|
|
||||||
|
if location := resp.Header.Get(common.HeaderLocation); location == "" {
|
||||||
|
tb.Errorf("%s %s = returns empty Location header, want non-empty", req.Method, req.RequestURI)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequest(t *testing.T) {
|
func TestRequest(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
req := new(http.Request)
|
req := new(delivery.Request)
|
||||||
if err := json.NewDecoder(strings.NewReader(`{
|
if err := json.NewDecoder(strings.NewReader(`{
|
||||||
"action": "update",
|
"action": "update",
|
||||||
"url": "http://example.com/",
|
"url": "http://example.com/",
|
||||||
|
@ -48,18 +137,18 @@ func TestContent_UnmarshalJSON(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
in string
|
in string
|
||||||
out http.Content
|
out delivery.Content
|
||||||
}{{
|
}{{
|
||||||
name: "plain",
|
name: "plain",
|
||||||
in: `"Hello World"`,
|
in: `"Hello World"`,
|
||||||
out: http.Content{
|
out: delivery.Content{
|
||||||
HTML: nil,
|
HTML: nil,
|
||||||
Value: "Hello World",
|
Value: "Hello World",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
name: "html",
|
name: "html",
|
||||||
in: `{"html":"<b>Hello</b> <i>World</i>"}`,
|
in: `{"html":"<b>Hello</b> <i>World</i>"}`,
|
||||||
out: http.Content{
|
out: delivery.Content{
|
||||||
HTML: testContent,
|
HTML: testContent,
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
|
@ -74,7 +163,7 @@ func TestContent_UnmarshalJSON(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if out == nil || len(out.Content) == 0 {
|
if len(out.Content) == 0 {
|
||||||
t.Error("empty content result, want not nil")
|
t.Error("empty content result, want not nil")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -96,26 +185,26 @@ func TestContent_MarshalJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
in http.Content
|
in delivery.Content
|
||||||
out string
|
out string
|
||||||
name string
|
name string
|
||||||
}{{
|
}{{
|
||||||
name: "plain",
|
name: "plain",
|
||||||
in: http.Content{
|
in: delivery.Content{
|
||||||
HTML: nil,
|
HTML: nil,
|
||||||
Value: `Hello World`,
|
Value: `Hello World`,
|
||||||
},
|
},
|
||||||
out: `{"content":["Hello World"]}`,
|
out: `{"content":["Hello World"]}`,
|
||||||
}, {
|
}, {
|
||||||
name: "html",
|
name: "html",
|
||||||
in: http.Content{
|
in: delivery.Content{
|
||||||
HTML: testContent,
|
HTML: testContent,
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
out: `{"content":[{"html":"\u003cb\u003eHello\u003c/b\u003e \u003ci\u003eWorld\u003c/i\u003e"}]}`,
|
out: `{"content":[{"html":"\u003cb\u003eHello\u003c/b\u003e \u003ci\u003eWorld\u003c/i\u003e"}]}`,
|
||||||
}, {
|
}, {
|
||||||
name: "both",
|
name: "both",
|
||||||
in: http.Content{
|
in: delivery.Content{
|
||||||
HTML: testContent,
|
HTML: testContent,
|
||||||
Value: `Hello World`,
|
Value: `Hello World`,
|
||||||
},
|
},
|
||||||
|
@ -127,7 +216,7 @@ func TestContent_MarshalJSON(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
out, err := json.Marshal(testRequest{
|
out, err := json.Marshal(testRequest{
|
||||||
Content: []http.Content{tc.in},
|
Content: []delivery.Content{tc.in},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -146,22 +235,22 @@ func TestDelete_UnmarshalJSON(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
in string
|
in string
|
||||||
out http.Delete
|
out delivery.Delete
|
||||||
}{{
|
}{{
|
||||||
name: "values",
|
name: "values",
|
||||||
in: `{"category":["indieweb"]}`,
|
in: `{"category":["indieweb"]}`,
|
||||||
out: http.Delete{
|
out: delivery.Delete{
|
||||||
Keys: nil,
|
Keys: nil,
|
||||||
Values: http.Properties{
|
Values: delivery.Properties{
|
||||||
Category: []string{"indieweb"},
|
Category: []string{"indieweb"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
name: "keys",
|
name: "keys",
|
||||||
in: `["category"]`,
|
in: `["category"]`,
|
||||||
out: http.Delete{
|
out: delivery.Delete{
|
||||||
Keys: []string{"category"},
|
Keys: []string{"category"},
|
||||||
Values: http.Properties{},
|
Values: delivery.Properties{},
|
||||||
},
|
},
|
||||||
}} {
|
}} {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
@ -187,11 +276,11 @@ func TestFigure_UnmarshalJSON(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
in string
|
in string
|
||||||
out http.Figure
|
out delivery.Figure
|
||||||
}{{
|
}{{
|
||||||
name: "alt",
|
name: "alt",
|
||||||
in: `{"value":"https://photos.example.com/globe.gif","alt":"Spinning globe animation"}`,
|
in: `{"value":"https://photos.example.com/globe.gif","alt":"Spinning globe animation"}`,
|
||||||
out: http.Figure{
|
out: delivery.Figure{
|
||||||
Alt: "Spinning globe animation",
|
Alt: "Spinning globe animation",
|
||||||
Value: &url.URL{
|
Value: &url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
|
@ -202,7 +291,7 @@ func TestFigure_UnmarshalJSON(t *testing.T) {
|
||||||
}, {
|
}, {
|
||||||
name: "plain",
|
name: "plain",
|
||||||
in: `"https://photos.example.com/592829482876343254.jpg"`,
|
in: `"https://photos.example.com/592829482876343254.jpg"`,
|
||||||
out: http.Figure{
|
out: delivery.Figure{
|
||||||
Alt: "",
|
Alt: "",
|
||||||
Value: &url.URL{
|
Value: &url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
|
|
|
@ -11,7 +11,7 @@ type (
|
||||||
UseCase interface {
|
UseCase interface {
|
||||||
// Create creates a new entry. Returns map or rel links, like Permalink
|
// Create creates a new entry. Returns map or rel links, like Permalink
|
||||||
// or created post, shortcode and syndication.
|
// or created post, shortcode and syndication.
|
||||||
Create(ctx context.Context, e domain.Entry) (map[string]*url.URL, error)
|
Create(ctx context.Context, e domain.Entry) (*domain.Entry, error)
|
||||||
|
|
||||||
// Update updates exist entry properties on provided u.
|
// Update updates exist entry properties on provided u.
|
||||||
//
|
//
|
||||||
|
@ -28,27 +28,55 @@ type (
|
||||||
Source(ctx context.Context, u *url.URL) (*domain.Entry, error)
|
Source(ctx context.Context, u *url.URL) (*domain.Entry, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
stubUseCase struct{}
|
dummyUseCase struct{}
|
||||||
|
|
||||||
|
stubUseCase struct {
|
||||||
|
entry *domain.Entry
|
||||||
|
err error
|
||||||
|
ok bool
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewStubUseCase() *stubUseCase {
|
func NewDummyUseCase() *dummyUseCase {
|
||||||
return &stubUseCase{}
|
return &dummyUseCase{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ucase *stubUseCase) Create(ctx context.Context, e domain.Entry) (map[string]*url.URL, error) {
|
func (dummyUseCase) Create(ctx context.Context, e domain.Entry) (map[string]*url.URL, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dummyUseCase) Update(ctx context.Context, u *url.URL, e domain.Entry) (*domain.Entry, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dummyUseCase) Delete(ctx context.Context, u *url.URL) (bool, error) { return false, nil }
|
||||||
|
func (dummyUseCase) Undelete(ctx context.Context, u *url.URL) (*domain.Entry, error) { return nil, nil }
|
||||||
|
func (dummyUseCase) Source(ctx context.Context, u *url.URL) (*domain.Entry, error) { return nil, nil }
|
||||||
|
|
||||||
|
func NewStubUseCase(err error, e *domain.Entry, ok bool) *stubUseCase {
|
||||||
|
return &stubUseCase{
|
||||||
|
entry: e,
|
||||||
|
err: err,
|
||||||
|
ok: ok,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ucase *stubUseCase) Create(ctx context.Context, e domain.Entry) (*domain.Entry, error) {
|
||||||
|
return ucase.entry, ucase.err
|
||||||
|
}
|
||||||
|
|
||||||
func (ucase *stubUseCase) Update(ctx context.Context, u *url.URL, e domain.Entry) (*domain.Entry, error) {
|
func (ucase *stubUseCase) Update(ctx context.Context, u *url.URL, e domain.Entry) (*domain.Entry, error) {
|
||||||
return nil, nil
|
return ucase.entry, ucase.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ucase *stubUseCase) Delete(ctx context.Context, u *url.URL) (bool, error) { return false, nil }
|
func (ucase *stubUseCase) Delete(ctx context.Context, u *url.URL) (bool, error) {
|
||||||
|
return ucase.ok, ucase.err
|
||||||
|
}
|
||||||
|
|
||||||
func (ucase *stubUseCase) Undelete(ctx context.Context, u *url.URL) (*domain.Entry, error) {
|
func (ucase *stubUseCase) Undelete(ctx context.Context, u *url.URL) (*domain.Entry, error) {
|
||||||
return nil, nil
|
return ucase.entry, ucase.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ucase *stubUseCase) Source(ctx context.Context, u *url.URL) (*domain.Entry, error) {
|
func (ucase *stubUseCase) Source(ctx context.Context, u *url.URL) (*domain.Entry, error) {
|
||||||
return nil, nil
|
return ucase.entry, ucase.err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/pub/internal/common"
|
"source.toby3d.me/toby3d/pub/internal/common"
|
||||||
"source.toby3d.me/toby3d/pub/internal/domain"
|
"source.toby3d.me/toby3d/pub/internal/domain"
|
||||||
|
@ -31,21 +33,44 @@ func NewHandler(media media.UseCase, config domain.Config) *Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
default:
|
||||||
|
WriteError(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||||
|
case "", http.MethodGet:
|
||||||
|
h.handleDownload(w, r)
|
||||||
|
case http.MethodPost:
|
||||||
|
h.handleUpload(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "" && r.Method != http.MethodGet {
|
||||||
|
WriteError(w, "method MUST be "+http.MethodGet, http.StatusMethodNotAllowed)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := h.media.Download(r.Context(), r.RequestURI)
|
||||||
|
if err != nil {
|
||||||
|
WriteError(w, "cannot download media: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.ServeContent(w, r, out.LogicalName(), time.Time{}, bytes.NewReader(out.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
WriteError(w, "method MUST be "+http.MethodPost, http.StatusBadRequest)
|
WriteError(w, "method MUST be "+http.MethodPost, http.StatusMethodNotAllowed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaType, _, err := mime.ParseMediaType(r.Header.Get(common.HeaderContentType))
|
mediaType, _, err := mime.ParseMediaType(r.Header.Get(common.HeaderContentType))
|
||||||
if err != nil {
|
if err != nil || mediaType != common.MIMEMultipartForm {
|
||||||
WriteError(w, "Content-Type header MUST be "+common.MIMEMultipartForm, http.StatusBadRequest)
|
WriteError(w, common.HeaderContentType+" header MUST be "+common.MIMEMultipartForm,
|
||||||
|
http.StatusBadRequest)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if mediaType != common.MIMEMultipartForm {
|
|
||||||
WriteError(w, "Content-Type header MUST be "+common.MIMEMultipartForm, http.StatusBadRequest)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package http_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -13,7 +14,7 @@ import (
|
||||||
delivery "source.toby3d.me/toby3d/pub/internal/media/delivery/http"
|
delivery "source.toby3d.me/toby3d/pub/internal/media/delivery/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpload(t *testing.T) {
|
func TestHandler_Upload(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testConfig := domain.TestConfig(t)
|
testConfig := domain.TestConfig(t)
|
||||||
|
@ -41,7 +42,7 @@ func TestUpload(t *testing.T) {
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
delivery.NewHandler(
|
delivery.NewHandler(
|
||||||
media.NewStubUseCase(expect, testFile, nil), *testConfig).
|
media.NewStubUseCase(nil, testFile, expect), *testConfig).
|
||||||
ServeHTTP(w, req)
|
ServeHTTP(w, req)
|
||||||
|
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
|
@ -54,3 +55,37 @@ func TestUpload(t *testing.T) {
|
||||||
t.Errorf("%s %s = %s, want not empty", req.Method, req.RequestURI, location)
|
t.Errorf("%s %s = %s, want not empty", req.Method, req.RequestURI, location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandler_Download(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testConfig := domain.TestConfig(t)
|
||||||
|
testFile := domain.TestFile(t)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "https://example.com/media/"+testFile.LogicalName(), nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
delivery.NewHandler(
|
||||||
|
media.NewStubUseCase(nil, testFile, nil), *testConfig).
|
||||||
|
ServeHTTP(w, req)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("%s %s = %d, want %d", req.Method, req.RequestURI, resp.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType, mediaType := resp.Header.Get(common.HeaderContentType), testFile.MediaType()
|
||||||
|
if contentType != mediaType {
|
||||||
|
t.Errorf("%s %s = '%s', want '%s'", req.Method, req.RequestURI, contentType, mediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(body, testFile.Content) {
|
||||||
|
t.Error("stored and received file contents is not the same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@ type (
|
||||||
dummyUseCase struct{}
|
dummyUseCase struct{}
|
||||||
|
|
||||||
stubUseCase struct {
|
stubUseCase struct {
|
||||||
u *url.URL
|
u *url.URL
|
||||||
f *domain.File
|
file *domain.File
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,11 +35,11 @@ func (dummyUseCase) Upload(_ context.Context, _ domain.File) (*url.URL, error)
|
||||||
func (dummyUseCase) Download(_ context.Context, _ string) (*domain.File, error) { return nil, nil }
|
func (dummyUseCase) Download(_ context.Context, _ string) (*domain.File, error) { return nil, nil }
|
||||||
|
|
||||||
// NewDummyUseCase creates a stub use case what always returns provided input.
|
// NewDummyUseCase creates a stub use case what always returns provided input.
|
||||||
func NewStubUseCase(u *url.URL, f *domain.File, err error) UseCase {
|
func NewStubUseCase(err error, file *domain.File, u *url.URL) UseCase {
|
||||||
return &stubUseCase{
|
return &stubUseCase{
|
||||||
u: u,
|
u: u,
|
||||||
f: f,
|
file: file,
|
||||||
err: err,
|
err: err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,5 +48,5 @@ func (ucase stubUseCase) Upload(_ context.Context, _ domain.File) (*url.URL, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ucase stubUseCase) Download(_ context.Context, _ string) (*domain.File, error) {
|
func (ucase stubUseCase) Download(_ context.Context, _ string) (*domain.File, error) {
|
||||||
return ucase.f, ucase.err
|
return ucase.file, ucase.err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue