- commit
- 72b3683
- parent
- 82dc843
- author
- Eric Bower
- date
- 2024-10-16 20:18:04 +0000 UTC
chore(pgs): integration tests for web api (#152)
9 files changed,
+441,
-10
+1,
-1
1@@ -34,7 +34,7 @@ jobs:
2 args: -E goimports -E godot --timeout 10m
3 - name: Run tests
4 run: |
5- go test -v ./... -cover -race -coverprofile=coverage.out
6+ PICO_SECRET="danger" go test -v ./... -cover -race -coverprofile=coverage.out
7 go tool cover -func=coverage.out -o=coverage.out
8 build-main:
9 runs-on: ubuntu-22.04
+1,
-1
1@@ -613,7 +613,7 @@ func createMainRoutes() []shared.Route {
2 return routes
3 }
4
5-func handler(routes []shared.Route, client *Client) shared.ServeFn {
6+func handler(routes []shared.Route, client *Client) http.HandlerFunc {
7 return func(w http.ResponseWriter, r *http.Request) {
8 var allow []string
9
+282,
-0
1@@ -0,0 +1,282 @@
2+package stub
3+
4+import (
5+ "database/sql"
6+ "fmt"
7+ "log/slog"
8+ "time"
9+
10+ "github.com/picosh/pico/db"
11+)
12+
13+type StubDB struct {
14+ Logger *slog.Logger
15+}
16+
17+var _ db.DB = (*StubDB)(nil)
18+
19+func NewStubDB(logger *slog.Logger) *StubDB {
20+ d := &StubDB{
21+ Logger: logger,
22+ }
23+ d.Logger.Info("Connecting to test database")
24+ return d
25+}
26+
27+var notImpl = fmt.Errorf("not implemented")
28+
29+func (me *StubDB) RegisterUser(username, pubkey, comment string) (*db.User, error) {
30+ return nil, notImpl
31+}
32+
33+func (me *StubDB) RemoveUsers(userIDs []string) error {
34+ return notImpl
35+}
36+
37+func (me *StubDB) InsertPublicKey(userID, key, name string, tx *sql.Tx) error {
38+ return notImpl
39+}
40+
41+func (me *StubDB) UpdatePublicKey(pubkeyID, name string) (*db.PublicKey, error) {
42+ return nil, notImpl
43+}
44+
45+func (me *StubDB) FindPublicKeyForKey(key string) (*db.PublicKey, error) {
46+ return nil, notImpl
47+}
48+
49+func (me *StubDB) FindPublicKey(pubkeyID string) (*db.PublicKey, error) {
50+ return nil, notImpl
51+}
52+
53+func (me *StubDB) FindKeysForUser(user *db.User) ([]*db.PublicKey, error) {
54+ return []*db.PublicKey{}, notImpl
55+}
56+
57+func (me *StubDB) RemoveKeys(keyIDs []string) error {
58+ return notImpl
59+}
60+
61+func (me *StubDB) FindSiteAnalytics(space string) (*db.Analytics, error) {
62+ return nil, notImpl
63+}
64+
65+func (me *StubDB) FindPostsBeforeDate(date *time.Time, space string) ([]*db.Post, error) {
66+ return []*db.Post{}, notImpl
67+}
68+
69+func (me *StubDB) FindUserForKey(username string, key string) (*db.User, error) {
70+ return nil, notImpl
71+}
72+
73+func (me *StubDB) FindUser(userID string) (*db.User, error) {
74+ return nil, notImpl
75+}
76+
77+func (me *StubDB) ValidateName(name string) (bool, error) {
78+ return false, notImpl
79+}
80+
81+func (me *StubDB) FindUserForName(name string) (*db.User, error) {
82+ return nil, notImpl
83+}
84+
85+func (me *StubDB) FindUserForNameAndKey(name string, key string) (*db.User, error) {
86+ return nil, notImpl
87+}
88+
89+func (me *StubDB) FindUserForToken(token string) (*db.User, error) {
90+ return nil, notImpl
91+}
92+
93+func (me *StubDB) SetUserName(userID string, name string) error {
94+ return notImpl
95+}
96+
97+func (me *StubDB) FindPostWithFilename(filename string, persona_id string, space string) (*db.Post, error) {
98+ return nil, notImpl
99+}
100+
101+func (me *StubDB) FindPostWithSlug(slug string, user_id string, space string) (*db.Post, error) {
102+ return nil, notImpl
103+}
104+
105+func (me *StubDB) FindPost(postID string) (*db.Post, error) {
106+ return nil, notImpl
107+}
108+
109+func (me *StubDB) FindAllPosts(page *db.Pager, space string) (*db.Paginate[*db.Post], error) {
110+ return &db.Paginate[*db.Post]{}, notImpl
111+}
112+
113+func (me *StubDB) FindAllUpdatedPosts(page *db.Pager, space string) (*db.Paginate[*db.Post], error) {
114+ return &db.Paginate[*db.Post]{}, notImpl
115+}
116+
117+func (me *StubDB) InsertPost(post *db.Post) (*db.Post, error) {
118+ return nil, notImpl
119+}
120+
121+func (me *StubDB) UpdatePost(post *db.Post) (*db.Post, error) {
122+ return nil, notImpl
123+}
124+
125+func (me *StubDB) RemovePosts(postIDs []string) error {
126+ return notImpl
127+}
128+
129+func (me *StubDB) FindPostsForUser(page *db.Pager, userID string, space string) (*db.Paginate[*db.Post], error) {
130+ return &db.Paginate[*db.Post]{}, notImpl
131+}
132+
133+func (me *StubDB) FindAllPostsForUser(userID string, space string) ([]*db.Post, error) {
134+ return []*db.Post{}, notImpl
135+}
136+
137+func (me *StubDB) FindPosts() ([]*db.Post, error) {
138+ return []*db.Post{}, notImpl
139+}
140+
141+func (me *StubDB) FindExpiredPosts(space string) ([]*db.Post, error) {
142+ return []*db.Post{}, notImpl
143+}
144+
145+func (me *StubDB) FindUpdatedPostsForUser(userID string, space string) ([]*db.Post, error) {
146+ return []*db.Post{}, notImpl
147+}
148+
149+func (me *StubDB) Close() error {
150+ return notImpl
151+}
152+
153+func (me *StubDB) InsertVisit(view *db.AnalyticsVisits) error {
154+ return notImpl
155+}
156+
157+func (me *StubDB) VisitSummary(opts *db.SummaryOpts) (*db.SummaryVisits, error) {
158+ return &db.SummaryVisits{}, notImpl
159+}
160+
161+func (me *StubDB) FindUsers() ([]*db.User, error) {
162+ return []*db.User{}, notImpl
163+}
164+
165+func (me *StubDB) ReplaceTagsForPost(tags []string, postID string) error {
166+ return notImpl
167+}
168+
169+func (me *StubDB) ReplaceAliasesForPost(aliases []string, postID string) error {
170+ return notImpl
171+}
172+
173+func (me *StubDB) FindUserPostsByTag(page *db.Pager, tag, userID, space string) (*db.Paginate[*db.Post], error) {
174+ return &db.Paginate[*db.Post]{}, notImpl
175+}
176+
177+func (me *StubDB) FindPostsByTag(pager *db.Pager, tag, space string) (*db.Paginate[*db.Post], error) {
178+ return &db.Paginate[*db.Post]{}, notImpl
179+}
180+
181+func (me *StubDB) FindPopularTags(space string) ([]string, error) {
182+ return []string{}, notImpl
183+}
184+
185+func (me *StubDB) FindTagsForPost(postID string) ([]string, error) {
186+ return []string{}, notImpl
187+}
188+
189+func (me *StubDB) FindFeatureForUser(userID string, feature string) (*db.FeatureFlag, error) {
190+ return nil, notImpl
191+}
192+
193+func (me *StubDB) FindFeaturesForUser(userID string) ([]*db.FeatureFlag, error) {
194+ return []*db.FeatureFlag{}, notImpl
195+}
196+
197+func (me *StubDB) HasFeatureForUser(userID string, feature string) bool {
198+ return false
199+}
200+
201+func (me *StubDB) FindTotalSizeForUser(userID string) (int, error) {
202+ return 0, notImpl
203+}
204+
205+func (me *StubDB) InsertFeedItems(postID string, items []*db.FeedItem) error {
206+ return notImpl
207+}
208+
209+func (me *StubDB) FindFeedItemsByPostID(postID string) ([]*db.FeedItem, error) {
210+ return []*db.FeedItem{}, notImpl
211+}
212+
213+func (me *StubDB) InsertProject(userID, name, projectDir string) (string, error) {
214+ return "", notImpl
215+}
216+
217+func (me *StubDB) UpdateProject(userID, name string) error {
218+ return notImpl
219+}
220+
221+func (me *StubDB) UpdateProjectAcl(userID, name string, acl db.ProjectAcl) error {
222+ return notImpl
223+}
224+
225+func (me *StubDB) LinkToProject(userID, projectID, projectDir string, commit bool) error {
226+ return notImpl
227+}
228+
229+func (me *StubDB) RemoveProject(projectID string) error {
230+ return notImpl
231+}
232+
233+func (me *StubDB) FindProjectByName(userID, name string) (*db.Project, error) {
234+ return &db.Project{}, notImpl
235+}
236+
237+func (me *StubDB) FindProjectLinks(userID, name string) ([]*db.Project, error) {
238+ return []*db.Project{}, notImpl
239+}
240+
241+func (me *StubDB) FindProjectsByPrefix(userID, prefix string) ([]*db.Project, error) {
242+ return []*db.Project{}, notImpl
243+}
244+
245+func (me *StubDB) FindProjectsByUser(userID string) ([]*db.Project, error) {
246+ return []*db.Project{}, notImpl
247+}
248+
249+func (me *StubDB) FindAllProjects(page *db.Pager, by string) (*db.Paginate[*db.Project], error) {
250+ return &db.Paginate[*db.Project]{}, notImpl
251+}
252+
253+func (me *StubDB) InsertToken(userID, name string) (string, error) {
254+ return "", notImpl
255+}
256+
257+func (me *StubDB) UpsertToken(userID, name string) (string, error) {
258+ return "", notImpl
259+}
260+
261+func (me *StubDB) FindTokenByName(userID, name string) (string, error) {
262+ return "", notImpl
263+}
264+
265+func (me *StubDB) RemoveToken(tokenID string) error {
266+ return notImpl
267+}
268+
269+func (me *StubDB) FindTokensForUser(userID string) ([]*db.Token, error) {
270+ return []*db.Token{}, notImpl
271+}
272+
273+func (me *StubDB) InsertFeature(userID, name string, expiresAt time.Time) (*db.FeatureFlag, error) {
274+ return nil, notImpl
275+}
276+
277+func (me *StubDB) RemoveFeature(userID string, name string) error {
278+ return notImpl
279+}
280+
281+func (me *StubDB) AddPicoPlusUser(username, paymentType, txId string) error {
282+ return notImpl
283+}
M
go.mod
+1,
-1
1@@ -36,7 +36,7 @@ require (
2 github.com/muesli/reflow v0.3.0
3 github.com/muesli/termenv v0.15.3-0.20240912151726-82936c5ea257
4 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577
5- github.com/picosh/pobj v0.0.0-20241008013754-bbbfc341e2cf
6+ github.com/picosh/pobj v0.0.0-20241016194248-c39198b2ff23
7 github.com/picosh/pubsub v0.0.0-20241008010300-a63fd95dc8ed
8 github.com/picosh/send v0.0.0-20241008013240-6fdbff00f848
9 github.com/picosh/tunkit v0.0.0-20240709033345-8315d4f3cd0e
M
go.sum
+2,
-2
1@@ -265,8 +265,8 @@ github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1Gsh
2 github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
3 github.com/picosh/go-rsync-receiver v0.0.0-20240709135253-1daf4b12a9fc h1:bvcsoOvaNHPquFnRkdraEo7+8t6bW7nWEhlALnwZPdI=
4 github.com/picosh/go-rsync-receiver v0.0.0-20240709135253-1daf4b12a9fc/go.mod h1:i0iR3W4GSm1PuvVxB9OH32E5jP+CYkVb2NQSe0JCtlo=
5-github.com/picosh/pobj v0.0.0-20241008013754-bbbfc341e2cf h1:Ul+LuTVXRimpIneOHez05k7VOV/lDVw37I18rceEplw=
6-github.com/picosh/pobj v0.0.0-20241008013754-bbbfc341e2cf/go.mod h1:cF+eAl4G1vU+WOD8cYCKaxokHo6MWmbR8J4/SJnvESg=
7+github.com/picosh/pobj v0.0.0-20241016194248-c39198b2ff23 h1:NEJ5a4UXeF0/X7xmYNzXcwLQID9DwgazlqkMMC5zZ3M=
8+github.com/picosh/pobj v0.0.0-20241016194248-c39198b2ff23/go.mod h1:cF+eAl4G1vU+WOD8cYCKaxokHo6MWmbR8J4/SJnvESg=
9 github.com/picosh/pubsub v0.0.0-20241008010300-a63fd95dc8ed h1:aBJeQoLvq/V3hX6bgWjuuTmGzgbPNYuuwaCWU4aSJcU=
10 github.com/picosh/pubsub v0.0.0-20241008010300-a63fd95dc8ed/go.mod h1:ajolgob5MxlHdp5HllF7u3rTlCgER4InqfP7M/xl6HQ=
11 github.com/picosh/send v0.0.0-20241008013240-6fdbff00f848 h1:VWbjNNOqpJ8AB3zdw+M5+XC/SINooWLGi6WCozKwt1o=
+1,
-0
1@@ -249,6 +249,7 @@ func (h *AssetHandler) handle(logger *slog.Logger, w http.ResponseWriter, r *htt
2
3 attempts = append(attempts, fp.Filepath)
4 mimeType := storage.GetMimeType(fp.Filepath)
5+ logger = logger.With("filename", fp.Filepath)
6 var c io.ReadCloser
7 var err error
8 if strings.HasPrefix(mimeType, "image/") {
+119,
-0
1@@ -0,0 +1,119 @@
2+package pgs
3+
4+import (
5+ "fmt"
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+)
17+
18+var testUserID = "user-1"
19+var testUsername = "user"
20+
21+type ApiExample struct {
22+ name string
23+ path string
24+ want string
25+ status int
26+ contentType string
27+
28+ dbpool db.DB
29+ storage map[string]map[string]string
30+}
31+
32+type PgsDb struct {
33+ *stub.StubDB
34+}
35+
36+func NewPgsDb(logger *slog.Logger) *PgsDb {
37+ sb := stub.NewStubDB(logger)
38+ return &PgsDb{
39+ StubDB: sb,
40+ }
41+}
42+
43+func (p *PgsDb) FindUserForName(name string) (*db.User, error) {
44+ return &db.User{
45+ ID: testUserID,
46+ Name: testUsername,
47+ }, nil
48+}
49+
50+func (p *PgsDb) FindProjectByName(userID, name string) (*db.Project, error) {
51+ return &db.Project{
52+ ID: "project-1",
53+ UserID: userID,
54+ Name: name,
55+ ProjectDir: name,
56+ Username: testUsername,
57+ Acl: db.ProjectAcl{
58+ Type: "public",
59+ },
60+ }, nil
61+}
62+
63+func mkpath(path string) string {
64+ return fmt.Sprintf("https://%s-test.pgs.test%s", testUsername, path)
65+}
66+
67+func TestApiBasic(t *testing.T) {
68+ bucketName := shared.GetAssetBucketName(testUserID)
69+ cfg := NewConfigSite()
70+ cfg.Domain = "pgs.test"
71+ tt := []*ApiExample{
72+ {
73+ name: "basic",
74+ path: "/",
75+ want: "hello world!",
76+ status: http.StatusOK,
77+ contentType: "text/html",
78+
79+ dbpool: NewPgsDb(cfg.Logger),
80+ storage: map[string]map[string]string{
81+ bucketName: {
82+ "test/index.html": "hello world!",
83+ },
84+ },
85+ },
86+ }
87+
88+ for _, tc := range tt {
89+ t.Run(tc.name, func(t *testing.T) {
90+ request := httptest.NewRequest("GET", mkpath(tc.path), strings.NewReader(""))
91+ responseRecorder := httptest.NewRecorder()
92+
93+ st, _ := storage.NewStorageMemory(tc.storage)
94+ ch := make(chan *db.AnalyticsVisits)
95+ apiConfig := &shared.ApiConfig{
96+ Cfg: cfg,
97+ Dbpool: tc.dbpool,
98+ Storage: st,
99+ AnalyticsQueue: ch,
100+ }
101+ handler := shared.CreateServe(mainRoutes, createSubdomainRoutes(publicPerm), apiConfig)
102+ router := http.HandlerFunc(handler)
103+ router(responseRecorder, request)
104+
105+ if responseRecorder.Code != tc.status {
106+ t.Errorf("Want status '%d', got '%d'", tc.status, responseRecorder.Code)
107+ }
108+
109+ ct := responseRecorder.Header().Get("content-type")
110+ if ct != tc.contentType {
111+ t.Errorf("Want status '%s', got '%s'", tc.contentType, ct)
112+ }
113+
114+ body := strings.TrimSpace(responseRecorder.Body.String())
115+ if body != tc.want {
116+ t.Errorf("Want '%s', got '%s'", tc.want, body)
117+ }
118+ })
119+ }
120+}
1@@ -55,7 +55,6 @@ func CreatePProfRoutes(routes []Route) []Route {
2 )
3 }
4
5-type ServeFn func(http.ResponseWriter, *http.Request)
6 type ApiConfig struct {
7 Cfg *ConfigSite
8 Dbpool db.DB
9@@ -73,7 +72,7 @@ func (hc *ApiConfig) CreateCtx(prevCtx context.Context, subdomain string) contex
10 return ctx
11 }
12
13-func CreateServeBasic(routes []Route, ctx context.Context) ServeFn {
14+func CreateServeBasic(routes []Route, ctx context.Context) http.HandlerFunc {
15 return func(w http.ResponseWriter, r *http.Request) {
16 var allow []string
17 for _, route := range routes {
18@@ -132,7 +131,7 @@ func findRouteConfig(r *http.Request, routes []Route, subdomainRoutes []Route, c
19 return curRoutes, subdomain
20 }
21
22-func CreateServe(routes []Route, subdomainRoutes []Route, apiConfig *ApiConfig) ServeFn {
23+func CreateServe(routes []Route, subdomainRoutes []Route, apiConfig *ApiConfig) http.HandlerFunc {
24 return func(w http.ResponseWriter, r *http.Request) {
25 curRoutes, subdomain := findRouteConfig(r, routes, subdomainRoutes, apiConfig.Cfg)
26 ctx := apiConfig.CreateCtx(r.Context(), subdomain)
27@@ -143,11 +142,12 @@ func CreateServe(routes []Route, subdomainRoutes []Route, apiConfig *ApiConfig)
28
29 type ctxDBKey struct{}
30 type ctxStorageKey struct{}
31-type ctxKey struct{}
32 type ctxLoggerKey struct{}
33-type ctxSubdomainKey struct{}
34 type ctxCfg struct{}
35 type ctxAnalyticsQueue struct{}
36+
37+type ctxSubdomainKey struct{}
38+type ctxKey struct{}
39 type CtxSshKey struct{}
40
41 func GetSshCtx(r *http.Request) (ssh.Context, error) {
1@@ -0,0 +1,29 @@
2+package storage
3+
4+import (
5+ "io"
6+
7+ sst "github.com/picosh/pobj/storage"
8+)
9+
10+type StorageMemory struct {
11+ *sst.StorageMemory
12+}
13+
14+func NewStorageMemory(sto map[string]map[string]string) (*StorageMemory, error) {
15+ st, err := sst.NewStorageMemory(sto)
16+ if err != nil {
17+ return nil, err
18+ }
19+ return &StorageMemory{st}, nil
20+}
21+
22+func (s *StorageMemory) ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, string, error) {
23+ obj, _, err := s.GetObject(bucket, fpath)
24+ return obj, GetMimeType(fpath), err
25+}
26+
27+func (s *StorageMemory) GetObjectSize(bucket sst.Bucket, fpath string) (int64, error) {
28+ _, info, err := s.GetObject(bucket, fpath)
29+ return info.Size, err
30+}