repos / pico

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

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