diff --git a/test/deps.go b/test/test_internals/deps.go similarity index 82% rename from test/deps.go rename to test/test_internals/deps.go index 611f1bb935a1ea1209ce5bf335da1e799003b8f7..7691d1ee19041c4697bc08931779f5e971fb5c90 100644 --- a/test/deps.go +++ b/test/test_internals/deps.go @@ -1,4 +1,4 @@ -package test +package test_internals import ( "context" @@ -17,9 +17,10 @@ type ContainerDeps struct { ctx context.Context pgContainer *postgres.PostgresContainer redisContainer testcontainers.Container - homeservers []*SynapseDep - machines []*mmrContainer depNet *NetworkDep + + Homeservers []*SynapseDep + Machines []*mmrContainer } func MakeTestDeps() (*ContainerDeps, error) { @@ -54,10 +55,12 @@ func MakeTestDeps() (*ContainerDeps, error) { if err != nil { return nil, err } - pgConnStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable") + pgHost, err := pgContainer.ContainerIP(ctx) if err != nil { return nil, err } + // we can hardcode the port and most of the connection details because we're behind the docker network here + pgConnStr := fmt.Sprintf("host=%s port=5432 user=postgres password=test1234 dbname=mmr sslmode=disable", pgHost) // Start a redis container cwd, err := os.Getwd() @@ -89,32 +92,35 @@ func MakeTestDeps() (*ContainerDeps, error) { Homeservers: []mmrHomeserverTmplArgs{ { ServerName: syn1.ServerName, - ClientServerApiUrl: syn1.ClientServerApiUrl, + ClientServerApiUrl: syn1.InternalClientServerApiUrl, }, { ServerName: syn2.ServerName, - ClientServerApiUrl: syn2.ClientServerApiUrl, + ClientServerApiUrl: syn2.InternalClientServerApiUrl, }, }, RedisAddr: fmt.Sprintf("%s:%d", redisHost, 6379), // we're behind the network for redis PgConnectionString: pgConnStr, }) + if err != nil { + return nil, err + } return &ContainerDeps{ ctx: ctx, pgContainer: pgContainer, redisContainer: redisContainer, - homeservers: []*SynapseDep{syn1, syn2}, - machines: mmrs, + Homeservers: []*SynapseDep{syn1, syn2}, + Machines: mmrs, depNet: depNet, }, nil } func (c *ContainerDeps) Teardown() { - for _, mmr := range c.machines { + for _, mmr := range c.Machines { mmr.Teardown() } - for _, hs := range c.homeservers { + for _, hs := range c.Homeservers { hs.Teardown() } if err := c.redisContainer.Terminate(c.ctx); err != nil { diff --git a/test/deps_mmr.go b/test/test_internals/deps_mmr.go similarity index 70% rename from test/deps_mmr.go rename to test/test_internals/deps_mmr.go index 5e6a76148aa3749b0ce871d81430f4f46db9dba9..7ed34144c0aae04fc62c76a84b1f575187e9d71d 100644 --- a/test/deps_mmr.go +++ b/test/test_internals/deps_mmr.go @@ -1,7 +1,8 @@ -package test +package test_internals import ( "context" + "errors" "fmt" "log" "os" @@ -35,6 +36,40 @@ type mmrContainer struct { MachineId int } +var mmrCachedImage string + +func reuseMmrBuild(ctx context.Context) (string, error) { + if mmrCachedImage != "" { + return mmrCachedImage, nil + } + log.Println("[Test Deps] Building MMR image...") + buildReq := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Dockerfile: "Dockerfile", + Context: ".", + PrintBuildLog: true, + }, + }, + Started: false, + } + provider, err := buildReq.ProviderType.GetProvider(testcontainers.WithLogger(testcontainers.Logger)) + if err != nil { + return "", err + } + c, err := provider.CreateContainer(ctx, buildReq.ContainerRequest) + if err != nil { + return "", err + } + if dockerC, ok := c.(*testcontainers.DockerContainer); !ok { + return "", errors.New("failed to convert built MMR container to a DockerContainer") + } else { + mmrCachedImage = dockerC.Image + } + log.Println("[Test Deps] Cached build as ", mmrCachedImage) + return mmrCachedImage, nil +} + func makeMmrInstances(ctx context.Context, count int, depNet *NetworkDep, tmplArgs mmrTmplArgs) ([]*mmrContainer, error) { // Prepare a config template t, err := template.New("mmr.config.yaml").ParseFiles(path.Join(".", "test", "templates", "mmr.config.yaml")) @@ -61,6 +96,12 @@ func makeMmrInstances(ctx context.Context, count int, depNet *NetworkDep, tmplAr return nil, err } + // Cache the MMR image + mmrImage, err := reuseMmrBuild(ctx) + if err != nil { + return nil, err + } + // Start the containers (using the same DB and config) mmrs := make([]*mmrContainer, 0) for i := 0; i < count; i++ { @@ -68,9 +109,7 @@ func makeMmrInstances(ctx context.Context, count int, depNet *NetworkDep, tmplAr p, _ := nat.NewPort("tcp", "8000") container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - FromDockerfile: testcontainers.FromDockerfile{ - Dockerfile: "Dockerfile", - }, + Image: mmrImage, ExposedPorts: []string{"8000/tcp"}, Mounts: []testcontainers.ContainerMount{ testcontainers.BindMount(f.Name(), "/data/media-repo.yaml"), @@ -98,6 +137,7 @@ func makeMmrInstances(ctx context.Context, count int, depNet *NetworkDep, tmplAr } //goland:noinspection HttpUrlsUsage csApiUrl := fmt.Sprintf("http://%s:%d", mmrHost, mmrPort.Int()) + log.Println("@@@@@@@@@@@@@@@@@@@@@@ ", csApiUrl) // Create the container object mmrs = append(mmrs, &mmrContainer{ diff --git a/test/deps_network.go b/test/test_internals/deps_network.go similarity index 98% rename from test/deps_network.go rename to test/test_internals/deps_network.go index b9495cf4c9ba19c2257e5af79741cbf93e84f270..a77179d498be96abc07160a7cbf58761fada6618 100644 --- a/test/deps_network.go +++ b/test/test_internals/deps_network.go @@ -1,4 +1,4 @@ -package test +package test_internals import ( "context" diff --git a/test/deps_synapse.go b/test/test_internals/deps_synapse.go similarity index 52% rename from test/deps_synapse.go rename to test/test_internals/deps_synapse.go index 9ef346056ba4f2b03fbed1a93807e9bf8c59efd3..cb317126c0615c6ce6df1f7ecf4974984bdc9fdc 100644 --- a/test/deps_synapse.go +++ b/test/test_internals/deps_synapse.go @@ -1,9 +1,15 @@ -package test +package test_internals import ( + "bytes" "context" + "encoding/json" + "errors" "fmt" + "io" "log" + "net/http" + "net/url" "os" "path" "strings" @@ -28,8 +34,16 @@ type SynapseDep struct { synContainer testcontainers.Container tmpConfigPath string - ClientServerApiUrl string - ServerName string + InternalClientServerApiUrl string + ExternalClientServerApiUrl string + ServerName string + + AdminUserId string + AdminAccessToken string + UnprivilegedAliceUserId string + UnprivilegedAliceAccessToken string + UnprivilegedBobUserId string + UnprivilegedBobAccessToken string } type fixNetwork struct { @@ -121,7 +135,11 @@ func MakeSynapse(domainName string, depNet *NetworkDep) (*SynapseDep, error) { } // Prepare the CS API URL - synHost, err := synContainer.ContainerIP(ctx) + synHost, err := synContainer.Host(ctx) + if err != nil { + return nil, err + } + synIp, err := synContainer.ContainerIP(ctx) if err != nil { return nil, err } @@ -130,16 +148,105 @@ func MakeSynapse(domainName string, depNet *NetworkDep) (*SynapseDep, error) { return nil, err } //goland:noinspection HttpUrlsUsage - csApiUrl := fmt.Sprintf("http://%s:%d", synHost, synPort.Int()) + intCsApiUrl := fmt.Sprintf("http://%s:%d", synIp, 8008) + extCsApiUrl := fmt.Sprintf("http://%s:%d", synHost, synPort.Int()) + + // Register the accounts + registerUser := func(localpart string, admin bool) (string, string, error) { // userId, accessToken, err + adminFlag := "--admin" + if !admin { + adminFlag = "--no-admin" + } + cmd := fmt.Sprintf("register_new_matrix_user -c /data/homeserver.yaml -u %s -p test1234 %s", localpart, adminFlag) + log.Println("[Synapse Command] " + cmd) + i, r, err := synContainer.Exec(ctx, strings.Split(cmd, " ")) + if err != nil { + return "", "", err + } + b, err := io.ReadAll(r) + if err != nil { + return "", "", err + } + if i != 0 { + return "", "", errors.New(string(b)) + } + + // Get user ID and access token from admin API + log.Println("[Synapse API] Logging in") + endpoint, err := url.JoinPath(extCsApiUrl, "/_matrix/client/v3/login") + if err != nil { + return "", "", err + } + b, err = json.Marshal(map[string]interface{}{ + "type": "m.login.password", + "identifier": map[string]interface{}{ + "type": "m.id.user", + "user": localpart, + }, + "password": "test1234", + "refresh_token": false, + }) + if err != nil { + return "", "", err + } + res, err := http.DefaultClient.Post(endpoint, "application/json", bytes.NewBuffer(b)) + if err != nil { + return "", "", err + } + b, err = io.ReadAll(res.Body) + if err != nil { + return "", "", err + } + if res.StatusCode != http.StatusOK { + return "", "", errors.New(res.Status + "\n" + string(b)) + } + log.Println("[Synapse API] " + string(b)) + m := make(map[string]interface{}) + err = json.Unmarshal(b, &m) + if err != nil { + return "", "", err + } + + var userId interface{} + var accessToken interface{} + var ok bool + if userId, ok = m["user_id"]; !ok { + return "", "", errors.New("missing user_id") + } + if accessToken, ok = m["access_token"]; !ok { + return "", "", errors.New("missing access_token") + } + + return userId.(string), accessToken.(string), nil + } + adminUserId, adminAccessToken, err := registerUser("admin", true) + if err != nil { + return nil, err + } + aliceUserId, aliceAccessToken, err := registerUser("user_alice", false) + if err != nil { + return nil, err + } + bobUserId, bobAccessToken, err := registerUser("user_bob", false) + if err != nil { + return nil, err + } // Create the dependency return &SynapseDep{ - ctx: ctx, - pgContainer: pgContainer, - synContainer: synContainer, - tmpConfigPath: f.Name(), - ClientServerApiUrl: csApiUrl, - ServerName: domainName, + ctx: ctx, + pgContainer: pgContainer, + synContainer: synContainer, + tmpConfigPath: f.Name(), + InternalClientServerApiUrl: intCsApiUrl, + ExternalClientServerApiUrl: extCsApiUrl, + ServerName: domainName, + AdminUserId: adminUserId, + AdminAccessToken: adminAccessToken, + UnprivilegedAliceUserId: aliceUserId, + UnprivilegedAliceAccessToken: aliceAccessToken, + UnprivilegedBobUserId: bobUserId, + UnprivilegedBobAccessToken: bobAccessToken, }, nil } diff --git a/test/testcontainers_ext.go b/test/test_internals/testcontainers_ext.go similarity index 95% rename from test/testcontainers_ext.go rename to test/test_internals/testcontainers_ext.go index 3fccb7fa225a68c8d4d53f070c8204c68d1d175c..f32ea2f331b9911fb5c381fb363bdc46320b5d49 100644 --- a/test/testcontainers_ext.go +++ b/test/test_internals/testcontainers_ext.go @@ -1,4 +1,4 @@ -package test +package test_internals import "github.com/testcontainers/testcontainers-go" diff --git a/test/test_internals/util.go b/test/test_internals/util.go new file mode 100644 index 0000000000000000000000000000000000000000..fe0744b9c1c20c1cf963780613038e7975f39899 --- /dev/null +++ b/test/test_internals/util.go @@ -0,0 +1,60 @@ +package test_internals + +import ( + "bytes" + "fmt" + "image" + "image/color" + "io" + "testing" + + "github.com/disintegration/imaging" + "github.com/stretchr/testify/assert" +) + +var evenColor = color.RGBA{R: 255, G: 0, B: 0, A: 255} +var oddColor = color.RGBA{R: 0, G: 255, B: 0, A: 255} +var altColor = color.RGBA{R: 0, G: 0, B: 255, A: 255} + +func colorFor(x int, y int) color.Color { + c := oddColor + if (y%2.0) == 0 && (x%2.0) == 0 { + c = altColor + } else if (y%2.0) == 0 || (x%2.0) == 0 { + c = evenColor + } + return c +} + +func MakeTestImage(width int, height int) (string, io.Reader, error) { + img := image.NewNRGBA(image.Rect(0, 0, width, height)) + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + c := colorFor(x, y) + img.Set(x, y, c) + } + } + + b := bytes.NewBuffer(make([]byte, 0)) + err := imaging.Encode(b, img, imaging.PNG) + if err != nil { + return "", nil, err + } + + return "image/png", b, nil +} + +func AssertIsTestImage(t *testing.T, i io.Reader) { + img, _, err := image.Decode(i) + assert.NoError(t, err, "Error decoding image") + width := img.Bounds().Max.X + height := img.Bounds().Max.Y + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + c := colorFor(x, y) + if !assert.Equal(t, c, img.At(x, y), fmt.Sprintf("Wrong colour for pixel %d,%d", x, y)) { + return // don't print thousands of errors + } + } + } +} diff --git a/test/test_internals/util_client.go b/test/test_internals/util_client.go new file mode 100644 index 0000000000000000000000000000000000000000..59080654e0676048502ed10ab4c60750bca84216 --- /dev/null +++ b/test/test_internals/util_client.go @@ -0,0 +1,66 @@ +package test_internals + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + + "github.com/turt2live/matrix-media-repo/database" +) + +type MatrixClient struct { + AccessToken string + ClientServerUrl string +} + +func (c *MatrixClient) Upload(filename string, contentType string, body io.Reader) (*MatrixUploadResponse, error) { + j, err := c.DoReturnJson("POST", "/_matrix/media/v3/upload", url.Values{"filename": []string{filename}}, contentType, body) + if err != nil { + return nil, err + } + val := new(MatrixUploadResponse) + err = j.ApplyTo(&val) + if err != nil { + return nil, err + } + return val, nil +} + +func (c *MatrixClient) DoReturnJson(method string, endpoint string, qs url.Values, contentType string, body io.Reader) (*database.AnonymousJson, error) { + res, err := c.DoRaw(method, endpoint, qs, contentType, body) + if err != nil { + return nil, err + } + + decoder := json.NewDecoder(res.Body) + val := new(database.AnonymousJson) + err = decoder.Decode(&val) + if err != nil { + return nil, err + } + + return val, nil +} + +func (c *MatrixClient) DoRaw(method string, endpoint string, qs url.Values, contentType string, body io.Reader) (*http.Response, error) { + endpoint, err := url.JoinPath(c.ClientServerUrl, endpoint) + if err != nil { + return nil, err + } + req, err := http.NewRequest(method, endpoint+"?"+qs.Encode(), body) + if err != nil { + return nil, err + } + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + if c.AccessToken != "" { + req.Header.Set("Authorization", "Bearer "+c.AccessToken) + } + + log.Println(fmt.Sprintf("[HTTP] [Auth=%s] %s %s", c.AccessToken, req.Method, req.RequestURI)) + return http.DefaultClient.Do(req) +} diff --git a/test/test_internals/util_client_api_types.go b/test/test_internals/util_client_api_types.go new file mode 100644 index 0000000000000000000000000000000000000000..5b2e79b6687f5ff8565c49e3ecb424350bb3a614 --- /dev/null +++ b/test/test_internals/util_client_api_types.go @@ -0,0 +1,5 @@ +package test_internals + +type MatrixUploadResponse struct { + MxcUri string `json:"content_uri"` +} diff --git a/test/upload_suite_test.go b/test/upload_suite_test.go index 6386b36293192fd135ec91361e5b3e6fa21422a5..45456563529ac0e7844186eb886283cf531098d1 100644 --- a/test/upload_suite_test.go +++ b/test/upload_suite_test.go @@ -6,15 +6,17 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/turt2live/matrix-media-repo/test/test_internals" + "github.com/turt2live/matrix-media-repo/util" ) type UploadTestSuite struct { suite.Suite - deps *ContainerDeps + deps *test_internals.ContainerDeps } func (s *UploadTestSuite) SetupSuite() { - deps, err := MakeTestDeps() + deps, err := test_internals.MakeTestDeps() if err != nil { log.Fatal(err) } @@ -30,7 +32,16 @@ func (s *UploadTestSuite) TearDownSuite() { func (s *UploadTestSuite) TestUpload() { t := s.T() - assert.NoError(t, nil) + client := &test_internals.MatrixClient{ + AccessToken: s.deps.Homeservers[0].UnprivilegedAliceAccessToken, + ClientServerUrl: s.deps.Machines[0].HttpUrl, + } + + contentType, img, err := test_internals.MakeTestImage(512, 512) + res, err := client.Upload("image"+util.ExtensionForContentType(contentType), contentType, img) + assert.NoError(t, err) + log.Println(res.MxcUri) + assert.NotEmpty(t, res.MxcUri) } func TestUploadTestSuite(t *testing.T) {