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}