mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-08 07:23:10 -06:00
cmd/mediaapi-integration-tests: Add foundation for testing
This commit is contained in:
parent
2d202cec07
commit
85fcef2968
|
|
@ -15,6 +15,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -36,27 +37,29 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bindAddr = os.Getenv("BIND_ADDRESS")
|
bindAddr = flag.String("listen", "", "The port to listen on.")
|
||||||
dataSource = os.Getenv("DATABASE")
|
dataSource = os.Getenv("DATABASE")
|
||||||
logDir = os.Getenv("LOG_DIR")
|
logDir = os.Getenv("LOG_DIR")
|
||||||
serverName = os.Getenv("SERVER_NAME")
|
serverName = os.Getenv("SERVER_NAME")
|
||||||
basePath = os.Getenv("BASE_PATH")
|
basePath = os.Getenv("BASE_PATH")
|
||||||
// Note: if the MAX_FILE_SIZE_BYTES is set to 0, it will be unlimited
|
// Note: if the MAX_FILE_SIZE_BYTES is set to 0, it will be unlimited
|
||||||
maxFileSizeBytesString = os.Getenv("MAX_FILE_SIZE_BYTES")
|
maxFileSizeBytesString = os.Getenv("MAX_FILE_SIZE_BYTES")
|
||||||
configPath = os.Getenv("CONFIG_PATH")
|
configPath = flag.String("config", "", "The path to the config file. For more information, see the config file in this repository.")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
common.SetupLogging(logDir)
|
common.SetupLogging(logDir)
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"BIND_ADDRESS": bindAddr,
|
"listen": *bindAddr,
|
||||||
"DATABASE": dataSource,
|
"DATABASE": dataSource,
|
||||||
"LOG_DIR": logDir,
|
"LOG_DIR": logDir,
|
||||||
"SERVER_NAME": serverName,
|
"SERVER_NAME": serverName,
|
||||||
"BASE_PATH": basePath,
|
"BASE_PATH": basePath,
|
||||||
"MAX_FILE_SIZE_BYTES": maxFileSizeBytesString,
|
"MAX_FILE_SIZE_BYTES": maxFileSizeBytesString,
|
||||||
"CONFIG_PATH": configPath,
|
"config": *configPath,
|
||||||
}).Info("Loading configuration based on config file and environment variables")
|
}).Info("Loading configuration based on config file and environment variables")
|
||||||
|
|
||||||
cfg, err := configureServer()
|
cfg, err := configureServer()
|
||||||
|
|
@ -64,15 +67,10 @@ func main() {
|
||||||
log.WithError(err).Fatal("Invalid configuration")
|
log.WithError(err).Fatal("Invalid configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := storage.Open(cfg.DataSource)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Panic("Failed to open database")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"BIND_ADDRESS": bindAddr,
|
"listen": *bindAddr,
|
||||||
"LOG_DIR": logDir,
|
"LOG_DIR": logDir,
|
||||||
"CONFIG_PATH": configPath,
|
"CONFIG_PATH": *configPath,
|
||||||
"ServerName": cfg.ServerName,
|
"ServerName": cfg.ServerName,
|
||||||
"AbsBasePath": cfg.AbsBasePath,
|
"AbsBasePath": cfg.AbsBasePath,
|
||||||
"MaxFileSizeBytes": *cfg.MaxFileSizeBytes,
|
"MaxFileSizeBytes": *cfg.MaxFileSizeBytes,
|
||||||
|
|
@ -82,13 +80,21 @@ func main() {
|
||||||
"ThumbnailSizes": cfg.ThumbnailSizes,
|
"ThumbnailSizes": cfg.ThumbnailSizes,
|
||||||
}).Info("Starting mediaapi server with configuration")
|
}).Info("Starting mediaapi server with configuration")
|
||||||
|
|
||||||
|
db, err := storage.Open(cfg.DataSource)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Panic("Failed to open database")
|
||||||
|
}
|
||||||
|
|
||||||
routing.Setup(http.DefaultServeMux, http.DefaultClient, cfg, db)
|
routing.Setup(http.DefaultServeMux, http.DefaultClient, cfg, db)
|
||||||
log.Fatal(http.ListenAndServe(bindAddr, nil))
|
log.Fatal(http.ListenAndServe(*bindAddr, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// configureServer loads configuration from a yaml file and overrides with environment variables
|
// configureServer loads configuration from a yaml file and overrides with environment variables
|
||||||
func configureServer() (*config.MediaAPI, error) {
|
func configureServer() (*config.MediaAPI, error) {
|
||||||
cfg, err := loadConfig(configPath)
|
if *configPath == "" {
|
||||||
|
log.Fatal("--config must be supplied")
|
||||||
|
}
|
||||||
|
cfg, err := loadConfig(*configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Fatal("Invalid config file")
|
log.WithError(err).Fatal("Invalid config file")
|
||||||
}
|
}
|
||||||
|
|
@ -172,14 +178,14 @@ func applyOverrides(cfg *config.MediaAPI) {
|
||||||
if cfg.MaxThumbnailGenerators == 0 {
|
if cfg.MaxThumbnailGenerators == 0 {
|
||||||
log.WithField(
|
log.WithField(
|
||||||
"max_thumbnail_generators", cfg.MaxThumbnailGenerators,
|
"max_thumbnail_generators", cfg.MaxThumbnailGenerators,
|
||||||
).Info("Using default max_thumbnail_generators")
|
).Info("Using default max_thumbnail_generators value of 10")
|
||||||
cfg.MaxThumbnailGenerators = 10
|
cfg.MaxThumbnailGenerators = 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateConfig(cfg *config.MediaAPI) error {
|
func validateConfig(cfg *config.MediaAPI) error {
|
||||||
if bindAddr == "" {
|
if *bindAddr == "" {
|
||||||
return fmt.Errorf("no BIND_ADDRESS environment variable found")
|
log.Fatal("--listen must be supplied")
|
||||||
}
|
}
|
||||||
|
|
||||||
absBasePath, err := getAbsolutePath(cfg.BasePath)
|
absBasePath, err := getAbsolutePath(cfg.BasePath)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
* functional
|
||||||
|
* upload
|
||||||
|
* normal case
|
||||||
|
* file too large
|
||||||
|
* 0-byte file?
|
||||||
|
* invalid filename
|
||||||
|
* invalid content-type
|
||||||
|
* download
|
||||||
|
* invalid origin
|
||||||
|
* invalid media id
|
||||||
|
* local file
|
||||||
|
* existing
|
||||||
|
* non-existing
|
||||||
|
* remote file
|
||||||
|
* existing
|
||||||
|
* non-existing
|
||||||
|
* thumbnail
|
||||||
|
* original file formats
|
||||||
|
* JPEG
|
||||||
|
* GIF
|
||||||
|
* PNG
|
||||||
|
* BMP
|
||||||
|
* SVG
|
||||||
|
* PDF
|
||||||
|
* local file
|
||||||
|
* existing
|
||||||
|
* non-existing
|
||||||
|
* remote file
|
||||||
|
* existing
|
||||||
|
* non-existing
|
||||||
|
* cache
|
||||||
|
* cold
|
||||||
|
* hot
|
||||||
|
* pre-generation according to configuration
|
||||||
|
* manual verification + hash check for regressions?
|
||||||
|
* dynamic generation
|
||||||
|
* cold cache
|
||||||
|
* hot cache
|
||||||
|
* larger than original
|
||||||
|
* limit on dimensions?
|
||||||
|
* 0x0
|
||||||
|
* scale
|
||||||
|
* crop
|
||||||
|
* load
|
||||||
|
* 100 parallel requests
|
||||||
|
* same file
|
||||||
|
* different local files
|
||||||
|
* different remote files
|
||||||
|
* pre-generated thumbnails
|
||||||
|
* non-pre-generated thumbnails
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// How long to wait for the server to write the expected output messages.
|
||||||
|
// This needs to be high enough to account for the time it takes to create
|
||||||
|
// the postgres database tables which can take a while on travis.
|
||||||
|
timeoutString = test.Defaulting(os.Getenv("TIMEOUT"), "10s")
|
||||||
|
// The name of maintenance database to connect to in order to create the test database.
|
||||||
|
postgresDatabase = test.Defaulting(os.Getenv("POSTGRES_DATABASE"), "postgres")
|
||||||
|
// The name of the test database to create.
|
||||||
|
testDatabaseName = test.Defaulting(os.Getenv("DATABASE_NAME"), "mediaapi_test")
|
||||||
|
// Postgres docker container name (for running psql)
|
||||||
|
postgresContainerName = os.Getenv("POSTGRES_CONTAINER")
|
||||||
|
)
|
||||||
|
|
||||||
|
var thumbnailPregenerationConfig = (`
|
||||||
|
thumbnail_sizes:
|
||||||
|
- width: 32
|
||||||
|
height: 32
|
||||||
|
method: crop
|
||||||
|
- width: 96
|
||||||
|
height: 96
|
||||||
|
method: crop
|
||||||
|
- width: 320
|
||||||
|
height: 240
|
||||||
|
method: scale
|
||||||
|
- width: 640
|
||||||
|
height: 480
|
||||||
|
method: scale
|
||||||
|
- width: 800
|
||||||
|
height: 600
|
||||||
|
method: scale
|
||||||
|
`)
|
||||||
|
|
||||||
|
const serverType = "media-api"
|
||||||
|
|
||||||
|
var testDatabaseTemplate = "dbname=%s sslmode=disable binary_parameters=yes"
|
||||||
|
|
||||||
|
var timeout time.Duration
|
||||||
|
|
||||||
|
func startMediaAPI(suffix string, dynamicThumbnails bool) (*exec.Cmd, chan error, string) {
|
||||||
|
dir, err := ioutil.TempDir("", serverType+"-server-test"+suffix)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configFilename := serverType + "-server-test-config" + suffix + ".yaml"
|
||||||
|
configFileContents := makeConfig(suffix, dir, dynamicThumbnails)
|
||||||
|
|
||||||
|
serverArgs := []string{
|
||||||
|
"--config", configFilename,
|
||||||
|
"--listen", "localhost:1777" + suffix,
|
||||||
|
}
|
||||||
|
|
||||||
|
databases := []string{
|
||||||
|
testDatabaseName + suffix,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, cmdChan := test.StartServer(
|
||||||
|
serverType,
|
||||||
|
serverArgs,
|
||||||
|
suffix,
|
||||||
|
configFilename,
|
||||||
|
configFileContents,
|
||||||
|
postgresDatabase,
|
||||||
|
postgresContainerName,
|
||||||
|
databases,
|
||||||
|
)
|
||||||
|
return cmd, cmdChan, dir
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeConfig(suffix, basePath string, dynamicThumbnails bool) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`
|
||||||
|
server_name: "%s"
|
||||||
|
base_path: %s
|
||||||
|
max_file_size_bytes: %s
|
||||||
|
database: "%s"
|
||||||
|
dynamic_thumbnails: %s
|
||||||
|
%s`,
|
||||||
|
"localhost:1777"+suffix,
|
||||||
|
basePath,
|
||||||
|
"10485760",
|
||||||
|
fmt.Sprintf(testDatabaseTemplate, testDatabaseName+suffix),
|
||||||
|
strconv.FormatBool(dynamicThumbnails),
|
||||||
|
thumbnailPregenerationConfig,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpServer(cmd *exec.Cmd, dir string) {
|
||||||
|
cmd.Process.Kill() // ensure server is dead, only cleaning up so don't care about errors this returns.
|
||||||
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
|
fmt.Printf("WARNING: Failed to remove temporary directory %v: %q\n", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs a battery of media API server tests
|
||||||
|
// The tests will pause at various points in this list to conduct tests on the HTTP responses before continuing.
|
||||||
|
func main() {
|
||||||
|
fmt.Println("==TESTING==", os.Args[0])
|
||||||
|
|
||||||
|
var err error
|
||||||
|
timeout, err = time.ParseDuration(timeoutString)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERROR: Invalid timeout string %v: %q\n", timeoutString, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create server1 with only pre-generated thumbnails allowed
|
||||||
|
server1Cmd, server1CmdChan, server1Dir := startMediaAPI("1", false)
|
||||||
|
defer cleanUpServer(server1Cmd, server1Dir)
|
||||||
|
testDownload("1", "localhost:17771", "doesnotexist", "", 404, server1CmdChan)
|
||||||
|
|
||||||
|
// create server2 with dynamic thumbnail generation
|
||||||
|
server2Cmd, server2CmdChan, server2Dir := startMediaAPI("2", true)
|
||||||
|
defer cleanUpServer(server2Cmd, server2Dir)
|
||||||
|
testDownload("2", "localhost:17772", "doesnotexist", "", 404, server2CmdChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMediaURI(scheme, host, endpoint string, components []string) string {
|
||||||
|
pathComponents := []string{
|
||||||
|
host,
|
||||||
|
"api",
|
||||||
|
"_matrix",
|
||||||
|
"media",
|
||||||
|
"v1",
|
||||||
|
endpoint,
|
||||||
|
}
|
||||||
|
pathComponents = append(pathComponents, components...)
|
||||||
|
return scheme + path.Join(pathComponents...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDownload(suffix, origin, mediaID, wantedBody string, wantedStatusCode int, serverCmdChan chan error) {
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
"GET",
|
||||||
|
getMediaURI("http://", "localhost:1777"+suffix, "download", []string{
|
||||||
|
origin,
|
||||||
|
mediaID,
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
testReq := &test.Request{
|
||||||
|
Req: req,
|
||||||
|
WantedStatusCode: wantedStatusCode,
|
||||||
|
WantedBody: wantedBody,
|
||||||
|
}
|
||||||
|
testReq.Run("media-api", timeout, serverCmdChan)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue