cmd/mediaapi-integration-tests: Add foundation for testing

This commit is contained in:
Robert Swain 2017-06-07 14:47:31 +02:00
parent 2d202cec07
commit 85fcef2968
3 changed files with 250 additions and 16 deletions

View file

@ -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)

View file

@ -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

View file

@ -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)
}