Add client package #6
| @@ -2,8 +2,8 @@ pipeline: | |||||||
|   test: |   test: | ||||||
|     image: golang:latest |     image: golang:latest | ||||||
|     commands: |     commands: | ||||||
|       - go build ./cmd/client/client.go |       - go build -o gpaste-client ./cmd/client/client.go | ||||||
|       - go build ./cmd/server/server.go |       - go build -o gpaste-server ./cmd/server/server.go | ||||||
|       - go test -v ./... |       - go test -v ./... | ||||||
|       - go vet ./... |       - go vet ./... | ||||||
|     when: |     when: | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								api/http.go
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								api/http.go
									
									
									
									
									
								
							| @@ -119,13 +119,8 @@ func (s *HTTPServer) HandlerAPIFileGet(w http.ResponseWriter, r *http.Request) { | |||||||
|  |  | ||||||
| func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.Request) { | func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.Request) { | ||||||
| 	reqID := middleware.GetReqID(r.Context()) | 	reqID := middleware.GetReqID(r.Context()) | ||||||
| 	type resp struct { |  | ||||||
| 		Message string `json:"message"` |  | ||||||
| 		ID      string `json:"id"` |  | ||||||
| 		URL     string `json:"url"` |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var responses []resp | 	var responses []ResponseAPIFilePost | ||||||
|  |  | ||||||
| 	if err := r.ParseMultipartForm(1024 * 1024 * 10); err != nil { | 	if err := r.ParseMultipartForm(1024 * 1024 * 10); err != nil { | ||||||
| 		s.Logger.Warnw("Error parsing multipart form.", "req_id", reqID, "err", err) | 		s.Logger.Warnw("Error parsing multipart form.", "req_id", reqID, "err", err) | ||||||
| @@ -149,7 +144,7 @@ func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.R | |||||||
| 		} | 		} | ||||||
| 		s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "filename", f.OriginalFilename, "remote_addr", r.RemoteAddr) | 		s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "filename", f.OriginalFilename, "remote_addr", r.RemoteAddr) | ||||||
|  |  | ||||||
| 		responses = append(responses, resp{Message: "OK", ID: f.ID, URL: "TODO"}) | 		responses = append(responses, ResponseAPIFilePost{Message: "OK", ID: f.ID, URL: "TODO"}) | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -162,10 +157,7 @@ func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.R | |||||||
|  |  | ||||||
| func (s *HTTPServer) HandlerAPILogin(w http.ResponseWriter, r *http.Request) { | func (s *HTTPServer) HandlerAPILogin(w http.ResponseWriter, r *http.Request) { | ||||||
| 	reqID := middleware.GetReqID(r.Context()) | 	reqID := middleware.GetReqID(r.Context()) | ||||||
| 	expectedRequest := struct { | 	var expectedRequest RequestAPILogin | ||||||
| 		Username string `json:"username"` |  | ||||||
| 		Password string `json:"password"` |  | ||||||
| 	}{} |  | ||||||
| 	decoder := json.NewDecoder(r.Body) | 	decoder := json.NewDecoder(r.Body) | ||||||
| 	defer r.Body.Close() | 	defer r.Body.Close() | ||||||
| 	if err := decoder.Decode(&expectedRequest); err != nil { | 	if err := decoder.Decode(&expectedRequest); err != nil { | ||||||
| @@ -179,9 +171,7 @@ func (s *HTTPServer) HandlerAPILogin(w http.ResponseWriter, r *http.Request) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	response := struct { | 	response := ResponseAPILogin{ | ||||||
| 		Token string `json:"token"` |  | ||||||
| 	}{ |  | ||||||
| 		Token: token, | 		Token: token, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -193,17 +183,12 @@ func (s *HTTPServer) HandlerAPILogin(w http.ResponseWriter, r *http.Request) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| type RequestAPIUserCreate struct { |  | ||||||
| 	Username string `json:"username"` |  | ||||||
| 	Password string `json:"password"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *HTTPServer) HandlerAPIUserCreate(w http.ResponseWriter, r *http.Request) { | func (s *HTTPServer) HandlerAPIUserCreate(w http.ResponseWriter, r *http.Request) { | ||||||
| 	reqID := middleware.GetReqID(r.Context()) | 	reqID := middleware.GetReqID(r.Context()) | ||||||
| 	defer r.Body.Close() | 	defer r.Body.Close() | ||||||
|  |  | ||||||
| 	level, err := AuthLevelFromRequest(r) | 	role, err := RoleFromRequest(r) | ||||||
| 	if err != nil || level < gpaste.AuthLevelAdmin { | 	if err != nil || role != users.RoleAdmin { | ||||||
| 		w.WriteHeader(http.StatusUnauthorized) | 		w.WriteHeader(http.StatusUnauthorized) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -229,5 +214,6 @@ func (s *HTTPServer) HandlerAPIUserCreate(w http.ResponseWriter, r *http.Request | |||||||
| 		w.WriteHeader(http.StatusInternalServerError) | 		w.WriteHeader(http.StatusInternalServerError) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	w.WriteHeader(http.StatusAccepted) | ||||||
| 	s.Logger.Infow("Created user.", "req_id", reqID, "remote_addr", r.RemoteAddr, "username", req.Username) | 	s.Logger.Infow("Created user.", "req_id", reqID, "remote_addr", r.RemoteAddr, "username", req.Username) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								api/json.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								api/json.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | package api | ||||||
|  |  | ||||||
|  | type RequestAPIUserCreate struct { | ||||||
|  | 	Username string `json:"username"` | ||||||
|  | 	Password string `json:"password"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RequestAPILogin struct { | ||||||
|  | 	Username string `json:"username"` | ||||||
|  | 	Password string `json:"password"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ResponseAPILogin struct { | ||||||
|  | 	Token string `json:"token"` | ||||||
|  | } | ||||||
|  | type ResponseAPIFilePost struct { | ||||||
|  | 	Message string `json:"message"` | ||||||
|  | 	ID      string `json:"id"` | ||||||
|  | 	URL     string `json:"url"` | ||||||
|  | } | ||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"git.t-juice.club/torjus/gpaste" | 	"git.t-juice.club/torjus/gpaste" | ||||||
|  | 	"git.t-juice.club/torjus/gpaste/users" | ||||||
| 	"github.com/go-chi/chi/v5/middleware" | 	"github.com/go-chi/chi/v5/middleware" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -90,14 +91,14 @@ func UsernameFromRequest(r *http.Request) (string, error) { | |||||||
| 	return username, nil | 	return username, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func AuthLevelFromRequest(r *http.Request) (gpaste.AuthLevel, error) { | func RoleFromRequest(r *http.Request) (users.Role, error) { | ||||||
| 	rawLevel := r.Context().Value(authCtxAuthLevel) | 	rawLevel := r.Context().Value(authCtxAuthLevel) | ||||||
| 	if rawLevel == nil { | 	if rawLevel == nil { | ||||||
| 		return gpaste.AuthLevelUnset, fmt.Errorf("no username") | 		return users.RoleUnset, fmt.Errorf("no username") | ||||||
| 	} | 	} | ||||||
| 	level, ok := rawLevel.(gpaste.AuthLevel) | 	level, ok := rawLevel.(users.Role) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return gpaste.AuthLevelUnset, fmt.Errorf("no username") | 		return users.RoleUnset, fmt.Errorf("no username") | ||||||
| 	} | 	} | ||||||
| 	return level, nil | 	return level, nil | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								auth.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								auth.go
									
									
									
									
									
								
							| @@ -9,14 +9,6 @@ import ( | |||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AuthLevel int |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	AuthLevelUnset AuthLevel = iota |  | ||||||
| 	AuthLevelUser |  | ||||||
| 	AuthLevelAdmin |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type AuthService struct { | type AuthService struct { | ||||||
| 	users      users.UserStore | 	users      users.UserStore | ||||||
| 	hmacSecret []byte | 	hmacSecret []byte | ||||||
|   | |||||||
							
								
								
									
										167
									
								
								client/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								client/client.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | package client | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"mime/multipart" | ||||||
|  | 	"net/http" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"git.t-juice.club/torjus/gpaste/api" | ||||||
|  | 	"git.t-juice.club/torjus/gpaste/files" | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Client struct { | ||||||
|  | 	BaseURL   string | ||||||
|  | 	AuthToken string | ||||||
|  |  | ||||||
|  | 	httpClient http.Client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) Login(ctx context.Context, username, password string) error { | ||||||
|  | 	url := fmt.Sprintf("%s/api/login", c.BaseURL) | ||||||
|  | 	// TODO: Change timeout | ||||||
|  | 	ctx, cancel := context.WithTimeout(ctx, 10*time.Second) | ||||||
|  | 	defer cancel() | ||||||
|  |  | ||||||
|  | 	body := new(bytes.Buffer) | ||||||
|  | 	requestData := api.RequestAPILogin{ | ||||||
|  | 		Username: username, | ||||||
|  | 		Password: password, | ||||||
|  | 	} | ||||||
|  | 	encoder := json.NewEncoder(body) | ||||||
|  | 	if err := encoder.Encode(&requestData); err != nil { | ||||||
|  | 		return fmt.Errorf("error encoding response: %w", err) | ||||||
|  | 	} | ||||||
|  | 	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error creating request: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := c.httpClient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("unable to perform request: %s", err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode != http.StatusOK { | ||||||
|  | 		return fmt.Errorf("got non-ok response from server: %s", resp.Status) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var responseData api.ResponseAPILogin | ||||||
|  |  | ||||||
|  | 	decoder := json.NewDecoder(resp.Body) | ||||||
|  | 	if err := decoder.Decode(&responseData); err != nil { | ||||||
|  | 		return fmt.Errorf("unable to parse response: %s", err) | ||||||
|  | 	} | ||||||
|  | 	c.AuthToken = responseData.Token | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) UserCreate(ctx context.Context, username, password string) error { | ||||||
|  | 	url := fmt.Sprintf("%s/api/user", c.BaseURL) | ||||||
|  | 	body := new(bytes.Buffer) | ||||||
|  |  | ||||||
|  | 	requestData := &api.RequestAPIUserCreate{ | ||||||
|  | 		Username: username, | ||||||
|  | 		Password: password, | ||||||
|  | 	} | ||||||
|  | 	encoder := json.NewEncoder(body) | ||||||
|  | 	if err := encoder.Encode(requestData); err != nil { | ||||||
|  | 		return fmt.Errorf("error encoding response: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error creating request: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AuthToken)) | ||||||
|  |  | ||||||
|  | 	resp, err := c.httpClient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("unable to perform request: %s", err) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode != http.StatusAccepted { | ||||||
|  | 		return fmt.Errorf("got non-ok response from server: %s", resp.Status) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) Download(ctx context.Context, id string) (io.ReadCloser, error) { | ||||||
|  | 	url := fmt.Sprintf("%s/api/file/%s", c.BaseURL, id) | ||||||
|  |  | ||||||
|  | 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error creating request: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AuthToken)) | ||||||
|  |  | ||||||
|  | 	resp, err := c.httpClient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("unable to perform request: %s", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode != http.StatusOK { | ||||||
|  | 		return nil, fmt.Errorf("got non-ok response from server: %s", resp.Status) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return resp.Body, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) Upload(ctx context.Context, files ...*files.File) ([]api.ResponseAPIFilePost, error) { | ||||||
|  | 	url := fmt.Sprintf("%s/api/file", c.BaseURL) | ||||||
|  | 	client := &http.Client{} | ||||||
|  |  | ||||||
|  | 	// TODO: Change timeout | ||||||
|  | 	ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) | ||||||
|  | 	defer cancel() | ||||||
|  |  | ||||||
|  | 	// TODO: Improve buffering | ||||||
|  | 	buf := &bytes.Buffer{} | ||||||
|  | 	mw := multipart.NewWriter(buf) | ||||||
|  |  | ||||||
|  | 	for _, file := range files { | ||||||
|  | 		fw, err := mw.CreateFormFile(uuid.Must(uuid.NewRandom()).String(), file.OriginalFilename) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if _, err := io.Copy(fw, file.Body); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		file.Body.Close() | ||||||
|  | 	} | ||||||
|  | 	mw.Close() | ||||||
|  | 	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	req.Header.Add("Content-Type", mw.FormDataContentType()) | ||||||
|  |  | ||||||
|  | 	resp, err := client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	var expectedResp []api.ResponseAPIFilePost | ||||||
|  |  | ||||||
|  | 	decoder := json.NewDecoder(resp.Body) | ||||||
|  | 	if err := decoder.Decode(&expectedResp); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error decoding response: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, r := range expectedResp { | ||||||
|  | 		fmt.Printf("Uploaded file %s\n", r.ID) | ||||||
|  | 	} | ||||||
|  | 	return expectedResp, nil | ||||||
|  | } | ||||||
							
								
								
									
										165
									
								
								client/client_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								client/client_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | |||||||
|  | package client_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"git.t-juice.club/torjus/gpaste" | ||||||
|  | 	"git.t-juice.club/torjus/gpaste/api" | ||||||
|  | 	"git.t-juice.club/torjus/gpaste/client" | ||||||
|  | 	"git.t-juice.club/torjus/gpaste/files" | ||||||
|  | 	"git.t-juice.club/torjus/gpaste/users" | ||||||
|  | 	"github.com/google/go-cmp/cmp" | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestClient(t *testing.T) { | ||||||
|  | 	listener, err := net.Listen("tcp", ":0") | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	port := listener.Addr().(*net.TCPAddr).Port | ||||||
|  | 	cfg := &gpaste.ServerConfig{ | ||||||
|  | 		LogLevel:      "ERROR", | ||||||
|  | 		URL:           fmt.Sprintf("http://localhost:%d", port), | ||||||
|  | 		SigningSecret: "TEST", | ||||||
|  | 		Store:         &gpaste.ServerStoreConfig{Type: "memory"}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	srv := api.NewHTTPServer(cfg) | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		srv.Serve(listener) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	t.Cleanup(func() { | ||||||
|  | 		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||||||
|  | 		defer cancel() | ||||||
|  | 		srv.Shutdown(ctx) | ||||||
|  | 		listener.Close() | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	// Add users | ||||||
|  | 	username := "admin" | ||||||
|  | 	password := "admin" | ||||||
|  | 	user := &users.User{ | ||||||
|  | 		Username: username, | ||||||
|  | 		Role:     users.RoleAdmin, | ||||||
|  | 	} | ||||||
|  | 	if err := user.SetPassword(password); err != nil { | ||||||
|  | 		t.Fatalf("Error setting password: %s", err) | ||||||
|  | 	} | ||||||
|  | 	if err := srv.Users.Store(user); err != nil { | ||||||
|  | 		t.Fatalf("Error storing user: %s", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Run("Login", func(t *testing.T) { | ||||||
|  | 		client := client.Client{BaseURL: cfg.URL} | ||||||
|  | 		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||||||
|  | 		defer cancel() | ||||||
|  | 		if err := client.Login(ctx, username, password); err != nil { | ||||||
|  | 			t.Fatalf("Error logging in: %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		claims, err := srv.Auth.ValidateToken(client.AuthToken) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Errorf("unable to get claims from token: %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if claims.Role != user.Role { | ||||||
|  | 			t.Errorf("Claims have wrong role: %s", cmp.Diff(claims.Role, user.Role)) | ||||||
|  | 		} | ||||||
|  | 		if claims.Subject != username { | ||||||
|  | 			t.Errorf("Claims have wrong role: %s", cmp.Diff(claims.Subject, username)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		t.Run("UserCreate", func(t *testing.T) { | ||||||
|  | 			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||||||
|  | 			defer cancel() | ||||||
|  | 			username := "user" | ||||||
|  | 			password := "user" | ||||||
|  |  | ||||||
|  | 			if err := client.UserCreate(ctx, username, password); err != nil { | ||||||
|  | 				t.Fatalf("Error creating user: %s", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			user, err := srv.Users.Get(username) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatalf("Error getting new user: %s", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if user.Username != username { | ||||||
|  | 				t.Errorf("Username does not match.") | ||||||
|  | 			} | ||||||
|  | 			if err := user.ValidatePassword(password); err != nil { | ||||||
|  | 				t.Errorf("Unable to validate password: %s", err) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Upload", func(t *testing.T) { | ||||||
|  | 		client := client.Client{BaseURL: cfg.URL} | ||||||
|  | 		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||||||
|  | 		defer cancel() | ||||||
|  | 		fileContents := "this is the test file" | ||||||
|  | 		fileBody := io.NopCloser(strings.NewReader(fileContents)) | ||||||
|  | 		file := &files.File{ | ||||||
|  | 			OriginalFilename: "filename.txt", | ||||||
|  | 			Body:             fileBody, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		resp, err := client.Upload(ctx, file) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("Error uploading: %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		retrieved, err := srv.Files.Get(resp[0].ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("Error getting uploaded file from store: %s", err) | ||||||
|  | 		} | ||||||
|  | 		defer retrieved.Body.Close() | ||||||
|  |  | ||||||
|  | 		buf := new(strings.Builder) | ||||||
|  | 		if _, err := io.Copy(buf, retrieved.Body); err != nil { | ||||||
|  | 			t.Fatalf("error reading body from store: %s", err) | ||||||
|  | 		} | ||||||
|  | 		if buf.String() != fileContents { | ||||||
|  | 			t.Errorf("File contents does not match: %s", cmp.Diff(buf.String(), fileContents)) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Download", func(t *testing.T) { | ||||||
|  | 		client := client.Client{BaseURL: cfg.URL} | ||||||
|  | 		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||||||
|  | 		defer cancel() | ||||||
|  | 		fileContents := "this is the test file" | ||||||
|  | 		fileBody := io.NopCloser(strings.NewReader(fileContents)) | ||||||
|  | 		file := &files.File{ | ||||||
|  | 			ID:               uuid.NewString(), | ||||||
|  | 			OriginalFilename: "filename.txt", | ||||||
|  | 			Body:             fileBody, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := srv.Files.Store(file); err != nil { | ||||||
|  | 			t.Fatalf("Error putting file in store: %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		body, err := client.Download(ctx, file.ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("Error uploading: %s", err) | ||||||
|  | 		} | ||||||
|  | 		defer body.Close() | ||||||
|  |  | ||||||
|  | 		buf := new(strings.Builder) | ||||||
|  | 		if _, err := io.Copy(buf, body); err != nil { | ||||||
|  | 			t.Fatalf("error reading body from store: %s", err) | ||||||
|  | 		} | ||||||
|  | 		if buf.String() != fileContents { | ||||||
|  | 			t.Errorf("File contents does not match: %s", cmp.Diff(buf.String(), fileContents)) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user