repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

pico / pgs
Eric Bower · 15 Nov 24

web_test.go

  1package pgs
  2
  3import (
  4	"fmt"
  5	"io"
  6	"log/slog"
  7	"net/http"
  8	"net/http/httptest"
  9	"strings"
 10	"testing"
 11
 12	"github.com/picosh/pico/db"
 13	"github.com/picosh/pico/db/stub"
 14	"github.com/picosh/pico/shared"
 15	"github.com/picosh/pico/shared/storage"
 16	sst "github.com/picosh/pobj/storage"
 17)
 18
 19var testUserID = "user-1"
 20var testUsername = "user"
 21
 22type ApiExample struct {
 23	name        string
 24	path        string
 25	want        string
 26	status      int
 27	contentType string
 28
 29	dbpool  db.DB
 30	storage map[string]map[string]string
 31}
 32
 33type PgsDb struct {
 34	*stub.StubDB
 35}
 36
 37func NewPgsDb(logger *slog.Logger) *PgsDb {
 38	sb := stub.NewStubDB(logger)
 39	return &PgsDb{
 40		StubDB: sb,
 41	}
 42}
 43
 44func (p *PgsDb) FindUserForName(name string) (*db.User, error) {
 45	return &db.User{
 46		ID:   testUserID,
 47		Name: testUsername,
 48	}, nil
 49}
 50
 51func (p *PgsDb) FindProjectByName(userID, name string) (*db.Project, error) {
 52	return &db.Project{
 53		ID:         "project-1",
 54		UserID:     userID,
 55		Name:       name,
 56		ProjectDir: name,
 57		Username:   testUsername,
 58		Acl: db.ProjectAcl{
 59			Type: "public",
 60		},
 61	}, nil
 62}
 63
 64type PgsAnalyticsDb struct {
 65	*PgsDb
 66}
 67
 68func NewPgsAnalticsDb(logger *slog.Logger) *PgsAnalyticsDb {
 69	return &PgsAnalyticsDb{
 70		PgsDb: NewPgsDb(logger),
 71	}
 72}
 73
 74func (p *PgsAnalyticsDb) HasFeatureForUser(userID, feature string) bool {
 75	return true
 76}
 77
 78func mkpath(path string) string {
 79	return fmt.Sprintf("https://%s-test.pgs.test%s", testUsername, path)
 80}
 81
 82func TestApiBasic(t *testing.T) {
 83	bucketName := shared.GetAssetBucketName(testUserID)
 84	cfg := NewConfigSite()
 85	cfg.Domain = "pgs.test"
 86	tt := []*ApiExample{
 87		{
 88			name:        "basic",
 89			path:        "/",
 90			want:        "hello world!",
 91			status:      http.StatusOK,
 92			contentType: "text/html",
 93
 94			dbpool: NewPgsDb(cfg.Logger),
 95			storage: map[string]map[string]string{
 96				bucketName: {
 97					"test/index.html": "hello world!",
 98				},
 99			},
100		},
101		{
102			name:        "direct-file",
103			path:        "/test.html",
104			want:        "hello world!",
105			status:      http.StatusOK,
106			contentType: "text/html",
107
108			dbpool: NewPgsDb(cfg.Logger),
109			storage: map[string]map[string]string{
110				bucketName: {
111					"test/test.html": "hello world!",
112				},
113			},
114		},
115		{
116			name:        "subdir-301-redirect",
117			path:        "/subdir",
118			want:        `<a href="/subdir/">Moved Permanently</a>.`,
119			status:      http.StatusMovedPermanently,
120			contentType: "text/html; charset=utf-8",
121
122			dbpool: NewPgsDb(cfg.Logger),
123			storage: map[string]map[string]string{
124				bucketName: {
125					"test/subdir/index.html": "hello world!",
126				},
127			},
128		},
129		{
130			name:        "redirects-file-301",
131			path:        "/anything",
132			want:        `<a href="/about.html">Moved Permanently</a>.`,
133			status:      http.StatusMovedPermanently,
134			contentType: "text/html; charset=utf-8",
135
136			dbpool: NewPgsDb(cfg.Logger),
137			storage: map[string]map[string]string{
138				bucketName: {
139					"test/_redirects": "/anything /about.html 301",
140					"test/about.html": "hello world!",
141				},
142			},
143		},
144		{
145			name:        "subdir-direct",
146			path:        "/subdir/index.html",
147			want:        "hello world!",
148			status:      http.StatusOK,
149			contentType: "text/html",
150
151			dbpool: NewPgsDb(cfg.Logger),
152			storage: map[string]map[string]string{
153				bucketName: {
154					"test/subdir/index.html": "hello world!",
155				},
156			},
157		},
158		{
159			name:        "spa",
160			path:        "/anything",
161			want:        "hello world!",
162			status:      http.StatusOK,
163			contentType: "text/html",
164
165			dbpool: NewPgsDb(cfg.Logger),
166			storage: map[string]map[string]string{
167				bucketName: {
168					"test/_redirects": "/* /index.html 200",
169					"test/index.html": "hello world!",
170				},
171			},
172		},
173		{
174			name:        "not-found",
175			path:        "/anything",
176			want:        "404 not found",
177			status:      http.StatusNotFound,
178			contentType: "text/plain; charset=utf-8",
179
180			dbpool: NewPgsDb(cfg.Logger),
181			storage: map[string]map[string]string{
182				bucketName: {},
183			},
184		},
185		{
186			name:        "not-found-custom",
187			path:        "/anything",
188			want:        "boom!",
189			status:      http.StatusNotFound,
190			contentType: "text/html",
191
192			dbpool: NewPgsDb(cfg.Logger),
193			storage: map[string]map[string]string{
194				bucketName: {
195					"test/404.html": "boom!",
196				},
197			},
198		},
199		{
200			name:        "images",
201			path:        "/profile.jpg",
202			want:        "image",
203			status:      http.StatusOK,
204			contentType: "image/jpeg",
205
206			dbpool: NewPgsDb(cfg.Logger),
207			storage: map[string]map[string]string{
208				bucketName: {
209					"test/profile.jpg": "image",
210				},
211			},
212		},
213	}
214
215	for _, tc := range tt {
216		t.Run(tc.name, func(t *testing.T) {
217			request := httptest.NewRequest("GET", mkpath(tc.path), strings.NewReader(""))
218			responseRecorder := httptest.NewRecorder()
219
220			st, _ := storage.NewStorageMemory(tc.storage)
221			router := NewWebRouter(cfg, cfg.Logger, tc.dbpool, st)
222			router.ServeHTTP(responseRecorder, request)
223
224			if responseRecorder.Code != tc.status {
225				t.Errorf("Want status '%d', got '%d'", tc.status, responseRecorder.Code)
226			}
227
228			ct := responseRecorder.Header().Get("content-type")
229			if ct != tc.contentType {
230				t.Errorf("Want status '%s', got '%s'", tc.contentType, ct)
231			}
232
233			body := strings.TrimSpace(responseRecorder.Body.String())
234			if body != tc.want {
235				t.Errorf("Want '%s', got '%s'", tc.want, body)
236			}
237		})
238	}
239}
240
241type ImageStorageMemory struct {
242	*storage.StorageMemory
243	Opts  *storage.ImgProcessOpts
244	Fpath string
245}
246
247func (s *ImageStorageMemory) ServeObject(bucket sst.Bucket, fpath string, opts *storage.ImgProcessOpts) (io.ReadCloser, string, error) {
248	s.Opts = opts
249	s.Fpath = fpath
250	return io.NopCloser(strings.NewReader("hello world!")), "image/jpeg", nil
251}
252
253func TestImageManipulation(t *testing.T) {
254	bucketName := shared.GetAssetBucketName(testUserID)
255	cfg := NewConfigSite()
256	cfg.Domain = "pgs.test"
257
258	tt := []ApiExample{
259		{
260			name:        "root-img",
261			path:        "/app.jpg/s:500/rt:90",
262			want:        "hello world!",
263			status:      http.StatusOK,
264			contentType: "image/jpeg",
265
266			dbpool: NewPgsDb(cfg.Logger),
267			storage: map[string]map[string]string{
268				bucketName: {
269					"test/app.jpg": "hello world!",
270				},
271			},
272		},
273		{
274			name:        "root-subdir-img",
275			path:        "/subdir/app.jpg/rt:90/s:500",
276			want:        "hello world!",
277			status:      http.StatusOK,
278			contentType: "image/jpeg",
279
280			dbpool: NewPgsDb(cfg.Logger),
281			storage: map[string]map[string]string{
282				bucketName: {
283					"test/subdir/app.jpg": "hello world!",
284				},
285			},
286		},
287	}
288
289	for _, tc := range tt {
290		t.Run(tc.name, func(t *testing.T) {
291			request := httptest.NewRequest("GET", mkpath(tc.path), strings.NewReader(""))
292			responseRecorder := httptest.NewRecorder()
293
294			memst, _ := storage.NewStorageMemory(tc.storage)
295			st := &ImageStorageMemory{
296				StorageMemory: memst,
297				Opts: &storage.ImgProcessOpts{
298					Ratio: &storage.Ratio{},
299				},
300			}
301			router := NewWebRouter(cfg, cfg.Logger, tc.dbpool, st)
302			router.ServeHTTP(responseRecorder, request)
303
304			if responseRecorder.Code != tc.status {
305				t.Errorf("Want status '%d', got '%d'", tc.status, responseRecorder.Code)
306			}
307
308			ct := responseRecorder.Header().Get("content-type")
309			if ct != tc.contentType {
310				t.Errorf("Want status '%s', got '%s'", tc.contentType, ct)
311			}
312
313			body := strings.TrimSpace(responseRecorder.Body.String())
314			if body != tc.want {
315				t.Errorf("Want '%s', got '%s'", tc.want, body)
316			}
317
318			if st.Opts.Ratio.Width != 500 {
319				t.Errorf("Want ratio width '500', got '%d'", st.Opts.Ratio.Width)
320				return
321			}
322
323			if st.Opts.Rotate != 90 {
324				t.Errorf("Want rotate '90', got '%d'", st.Opts.Rotate)
325				return
326			}
327		})
328	}
329}