repos / pico

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

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
M go.mod
M go.sum
M .github/workflows/build.yml
+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
M auth/api.go
+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 
A db/stub/stub.go
+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=
M pgs/api.go
+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/") {
A pgs/api_test.go
+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+}
M shared/router.go
+5, -5
 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) {
A shared/storage/memory.go
+29, -0
 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+}