Skip to content

Commit 5437e43

Browse files
committed
feat: add tests
1 parent faf6119 commit 5437e43

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed

lib/httpapi/server_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package httpapi_test
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"io"
78
"log/slog"
9+
"mime/multipart"
810
"net/http"
911
"net/http/httptest"
1012
"os"
13+
"strings"
1114
"testing"
1215

1316
"github.com/coder/agentapi/lib/httpapi"
@@ -652,3 +655,188 @@ func assertSSEHeaders(t testing.TB, resp *http.Response) {
652655
assert.Equal(t, "no", resp.Header.Get("X-Proxy-Buffering"))
653656
assert.Equal(t, "keep-alive", resp.Header.Get("Connection"))
654657
}
658+
659+
func TestServer_UploadFiles(t *testing.T) {
660+
t.Parallel()
661+
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
662+
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
663+
AgentType: msgfmt.AgentTypeClaude,
664+
Process: nil,
665+
Port: 0,
666+
ChatBasePath: "/chat",
667+
AllowedHosts: []string{"*"},
668+
AllowedOrigins: []string{"*"},
669+
})
670+
require.NoError(t, err)
671+
tsServer := httptest.NewServer(srv.Handler())
672+
t.Cleanup(tsServer.Close)
673+
674+
cases := []struct {
675+
name string
676+
filename string
677+
fileContent string
678+
expectedStatusCode int
679+
expectFilePath bool
680+
}{
681+
{
682+
name: "upload jpeg file",
683+
filename: "test.jpeg",
684+
fileContent: "Hello, world!",
685+
expectedStatusCode: http.StatusOK,
686+
expectFilePath: true,
687+
},
688+
{
689+
name: "upload empty file",
690+
filename: "empty.txt",
691+
fileContent: "",
692+
expectedStatusCode: http.StatusOK,
693+
expectFilePath: true,
694+
},
695+
{
696+
name: "upload binary file",
697+
filename: "test.bin",
698+
fileContent: "\x00\x01\x02\x03\xFF",
699+
expectedStatusCode: http.StatusOK,
700+
expectFilePath: true,
701+
},
702+
{
703+
name: "upload file with special characters in name",
704+
filename: "test file (1).txt",
705+
fileContent: "content",
706+
expectedStatusCode: http.StatusOK,
707+
expectFilePath: true,
708+
},
709+
}
710+
711+
for _, tc := range cases {
712+
t.Run(tc.name, func(t *testing.T) {
713+
t.Parallel()
714+
715+
// Create a buffer to write our multipart form data
716+
var buf bytes.Buffer
717+
writer := multipart.NewWriter(&buf)
718+
719+
// Add the file field
720+
part, err := writer.CreateFormFile("file", tc.filename)
721+
require.NoError(t, err)
722+
_, err = part.Write([]byte(tc.fileContent))
723+
require.NoError(t, err)
724+
725+
// Close the writer to finalize the form
726+
err = writer.Close()
727+
require.NoError(t, err)
728+
729+
// Create the request
730+
req, err := http.NewRequest("POST", tsServer.URL+"/upload", &buf)
731+
require.NoError(t, err)
732+
req.Header.Set("Content-Type", writer.FormDataContentType())
733+
734+
// Send the request
735+
client := &http.Client{}
736+
resp, err := client.Do(req)
737+
require.NoError(t, err)
738+
t.Cleanup(func() {
739+
_ = resp.Body.Close()
740+
})
741+
742+
// Check status code
743+
require.Equal(t, tc.expectedStatusCode, resp.StatusCode,
744+
"expected status code %d, got %d", tc.expectedStatusCode, resp.StatusCode)
745+
746+
if tc.expectedStatusCode == http.StatusOK {
747+
// Parse response body
748+
body, err := io.ReadAll(resp.Body)
749+
require.NoError(t, err)
750+
751+
var uploadResp struct {
752+
Ok bool `json:"ok"`
753+
FilePath string `json:"filePath"`
754+
}
755+
err = json.Unmarshal(body, &uploadResp)
756+
require.NoError(t, err)
757+
758+
// Verify response
759+
require.True(t, uploadResp.Ok, "expected ok to be true")
760+
761+
if tc.expectFilePath {
762+
require.NotEmpty(t, uploadResp.FilePath, "expected file path to be non-empty")
763+
764+
// Verify file was actually saved
765+
savedContent, err := os.ReadFile(uploadResp.FilePath)
766+
require.NoError(t, err)
767+
require.Equal(t, tc.fileContent, string(savedContent), "file content should match")
768+
769+
// Verify filename is preserved in the path
770+
expectedFilename := tc.filename
771+
if expectedFilename == "" {
772+
expectedFilename = "uploaded_file"
773+
}
774+
require.Contains(t, uploadResp.FilePath, expectedFilename, "file path should contain filename")
775+
776+
// Clean up the uploaded file
777+
t.Cleanup(func() {
778+
_ = os.Remove(uploadResp.FilePath)
779+
})
780+
}
781+
}
782+
})
783+
}
784+
}
785+
786+
func TestServer_UploadFiles_Errors(t *testing.T) {
787+
t.Parallel()
788+
ctx := logctx.WithLogger(context.Background(), slog.New(slog.NewTextHandler(os.Stdout, nil)))
789+
srv, err := httpapi.NewServer(ctx, httpapi.ServerConfig{
790+
AgentType: msgfmt.AgentTypeClaude,
791+
Process: nil,
792+
Port: 0,
793+
ChatBasePath: "/chat",
794+
AllowedHosts: []string{"*"},
795+
AllowedOrigins: []string{"*"},
796+
})
797+
require.NoError(t, err)
798+
tsServer := httptest.NewServer(srv.Handler())
799+
t.Cleanup(tsServer.Close)
800+
801+
t.Run("missing file field", func(t *testing.T) {
802+
t.Parallel()
803+
804+
// Create multipart form without file field
805+
var buf bytes.Buffer
806+
writer := multipart.NewWriter(&buf)
807+
err := writer.WriteField("notfile", "value")
808+
require.NoError(t, err)
809+
err = writer.Close()
810+
require.NoError(t, err)
811+
812+
req, err := http.NewRequest("POST", tsServer.URL+"/upload", &buf)
813+
require.NoError(t, err)
814+
req.Header.Set("Content-Type", writer.FormDataContentType())
815+
816+
client := &http.Client{}
817+
resp, err := client.Do(req)
818+
require.NoError(t, err)
819+
t.Cleanup(func() {
820+
_ = resp.Body.Close()
821+
})
822+
823+
require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
824+
})
825+
826+
t.Run("invalid content type", func(t *testing.T) {
827+
t.Parallel()
828+
829+
req, err := http.NewRequest("POST", tsServer.URL+"/upload", strings.NewReader("not multipart"))
830+
require.NoError(t, err)
831+
req.Header.Set("Content-Type", "application/json")
832+
833+
client := &http.Client{}
834+
resp, err := client.Do(req)
835+
require.NoError(t, err)
836+
t.Cleanup(func() {
837+
_ = resp.Body.Close()
838+
})
839+
840+
require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
841+
})
842+
}

0 commit comments

Comments
 (0)