Add support for mocking rpmmd

We want to test API methods which calls dnf. Unfortunately, calling dnf
is expensive operation - it requires network access and downloading
a lot of (meta)data. This commit changes the rpmmd implementation
so that it can be mocked.
This commit is contained in:
Ondřej Budai 2019-11-12 20:14:32 +01:00 committed by Tom Gundersen
parent 9970150ed5
commit 495f5b558b
6 changed files with 97 additions and 23 deletions

View file

@ -38,7 +38,9 @@ func main() {
Metalink: "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=x86_64", Metalink: "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=x86_64",
} }
packages, err := rpmmd.FetchPackageList([]rpmmd.RepoConfig{repo}) rpm := rpmmd.NewRPMMD()
packages, err := rpm.FetchPackageList([]rpmmd.RepoConfig{repo})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -56,7 +58,7 @@ func main() {
store := store.New(&stateFile) store := store.New(&stateFile)
jobAPI := jobqueue.New(logger, store) jobAPI := jobqueue.New(logger, store)
weldrAPI := weldr.New(repo, packages, logger, store) weldrAPI := weldr.New(rpm, repo, packages, logger, store)
go jobAPI.Serve(jobListener) go jobAPI.Serve(jobListener)
weldrAPI.Serve(weldrListener) weldrAPI.Serve(weldrListener)

View file

@ -0,0 +1,19 @@
package rpmmd_mock
import (
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
var BaseFixture = Fixture{
fetchPackageList{
rpmmd.PackageList{
{Name: "package1"},
{Name: "package2"},
},
nil,
},
depsolve{
nil,
nil,
},
}

View file

@ -0,0 +1,33 @@
package rpmmd_mock
import "github.com/osbuild/osbuild-composer/internal/rpmmd"
type fetchPackageList struct {
ret rpmmd.PackageList
err error
}
type depsolve struct {
ret []rpmmd.PackageSpec
err error
}
type Fixture struct {
fetchPackageList
depsolve
}
type rpmmdMock struct {
Fixture Fixture
}
func NewRPMMDMock(fixture Fixture) rpmmd.RPMMD {
return &rpmmdMock{Fixture: fixture}
}
func (r *rpmmdMock) FetchPackageList(repos []rpmmd.RepoConfig) (rpmmd.PackageList, error) {
return r.Fixture.fetchPackageList.ret, r.Fixture.fetchPackageList.err
}
func (r *rpmmdMock) Depsolve(specs []string, repos []rpmmd.RepoConfig) ([]rpmmd.PackageSpec, error) {
return r.Fixture.depsolve.ret, r.Fixture.depsolve.err
}

View file

@ -39,6 +39,11 @@ type PackageSpec struct {
Arch string `json:"arch,omitempty"` Arch string `json:"arch,omitempty"`
} }
type RPMMD interface {
FetchPackageList(repos []RepoConfig) (PackageList, error)
Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, error)
}
func runDNF(command string, arguments interface{}, result interface{}) error { func runDNF(command string, arguments interface{}, result interface{}) error {
var call = struct { var call = struct {
Command string `json:"command"` Command string `json:"command"`
@ -80,7 +85,13 @@ func runDNF(command string, arguments interface{}, result interface{}) error {
return cmd.Wait() return cmd.Wait()
} }
func FetchPackageList(repos []RepoConfig) (PackageList, error) { type rpmmdImpl struct{}
func NewRPMMD() RPMMD {
return &rpmmdImpl{}
}
func (*rpmmdImpl) FetchPackageList(repos []RepoConfig) (PackageList, error) {
var arguments = struct { var arguments = struct {
Repos []RepoConfig `json:"repos"` Repos []RepoConfig `json:"repos"`
}{repos} }{repos}
@ -92,7 +103,7 @@ func FetchPackageList(repos []RepoConfig) (PackageList, error) {
return packages, err return packages, err
} }
func Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, error) { func (*rpmmdImpl) Depsolve(specs []string, repos []RepoConfig) ([]PackageSpec, error) {
var arguments = struct { var arguments = struct {
PackageSpecs []string `json:"package-specs"` PackageSpecs []string `json:"package-specs"`
Repos []RepoConfig `json:"repos"` Repos []RepoConfig `json:"repos"`

View file

@ -24,6 +24,7 @@ import (
type API struct { type API struct {
store *store.Store store *store.Store
rpmmd rpmmd.RPMMD
repo rpmmd.RepoConfig repo rpmmd.RepoConfig
packages rpmmd.PackageList packages rpmmd.PackageList
@ -31,11 +32,12 @@ type API struct {
router *httprouter.Router router *httprouter.Router
} }
func New(repo rpmmd.RepoConfig, packages rpmmd.PackageList, logger *log.Logger, store *store.Store) *API { func New(rpmmd rpmmd.RPMMD, repo rpmmd.RepoConfig, packages rpmmd.PackageList, logger *log.Logger, store *store.Store) *API {
// This needs to be shared with the worker API so that they can communicate with each other // This needs to be shared with the worker API so that they can communicate with each other
// builds := make(chan queue.Build, 200) // builds := make(chan queue.Build, 200)
api := &API{ api := &API{
store: store, store: store,
rpmmd: rpmmd,
repo: repo, repo: repo,
packages: packages, packages: packages,
logger: logger, logger: logger,
@ -467,7 +469,7 @@ func (api *API) modulesInfoHandler(writer http.ResponseWriter, request *http.Req
} }
if modulesRequested { if modulesRequested {
project.Dependencies, _ = rpmmd.Depsolve([]string{pkg.Name}, []rpmmd.RepoConfig{api.repo}) project.Dependencies, _ = api.rpmmd.Depsolve([]string{pkg.Name}, []rpmmd.RepoConfig{api.repo})
} }
projects = append(projects, project) projects = append(projects, project)
@ -610,7 +612,7 @@ func (api *API) blueprintsDepsolveHandler(writer http.ResponseWriter, request *h
specs[i] += "-*-*.*" specs[i] += "-*-*.*"
} }
} }
dependencies, _ := rpmmd.Depsolve(specs, []rpmmd.RepoConfig{api.repo}) dependencies, _ := api.rpmmd.Depsolve(specs, []rpmmd.RepoConfig{api.repo})
blueprints = append(blueprints, entry{blueprint, dependencies}) blueprints = append(blueprints, entry{blueprint, dependencies})
} }

View file

@ -16,6 +16,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/osbuild/osbuild-composer/internal/mocks/rpmmd"
"github.com/osbuild/osbuild-composer/internal/rpmmd" "github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/store" "github.com/osbuild/osbuild-composer/internal/store"
"github.com/osbuild/osbuild-composer/internal/weldr" "github.com/osbuild/osbuild-composer/internal/weldr"
@ -27,11 +28,6 @@ var repo = rpmmd.RepoConfig{
BaseURL: "http://example.com/test/os", BaseURL: "http://example.com/test/os",
} }
var packages = rpmmd.PackageList{
{Name: "package1"},
{Name: "package2"},
}
func externalRequest(method, path, body string) *http.Response { func externalRequest(method, path, body string) *http.Response {
client := http.Client{ client := http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
@ -161,6 +157,18 @@ func testRoute(t *testing.T, api *weldr.API, external bool, method, path, body s
} }
} }
func createWeldrAPI(fixture rpmmd_mock.Fixture) (*weldr.API, *store.Store) {
s := store.New(nil)
rpm := rpmmd_mock.NewRPMMDMock(fixture)
packageList, err := rpm.FetchPackageList([]rpmmd.RepoConfig{repo})
if err != nil {
panic(err)
}
return weldr.New(rpm, repo, packageList, nil, s), s
}
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
var cases = []struct { var cases = []struct {
Path string Path string
@ -197,7 +205,7 @@ func TestBasic(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
testRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON) testRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
} }
} }
@ -214,7 +222,7 @@ func TestBlueprintsNew(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON) testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
} }
} }
@ -231,7 +239,7 @@ func TestBlueprintsWorkspace(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`) sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON) testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
} }
@ -252,7 +260,7 @@ func TestBlueprintsInfo(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test1","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`) sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test1","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`) sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
sendHTTP(api, true, "POST", "/api/v0/blueprints/workspace", `{"name":"test2","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`) sendHTTP(api, true, "POST", "/api/v0/blueprints/workspace", `{"name":"test2","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`)
@ -274,7 +282,7 @@ func TestBlueprintsDiff(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`) sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
sendHTTP(api, true, "POST", "/api/v0/blueprints/workspace", `{"name":"test","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`) sendHTTP(api, true, "POST", "/api/v0/blueprints/workspace", `{"name":"test","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`)
testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON) testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
@ -294,7 +302,7 @@ func TestBlueprintsDelete(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`) sendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON) testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
sendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/test", ``) sendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/test", ``)
@ -331,7 +339,7 @@ func TestCompose(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
sendHTTP(api, c.External, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`) sendHTTP(api, c.External, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
testRoute(t, api, c.External, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, c.IgnoreFields...) testRoute(t, api, c.External, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, c.IgnoreFields...)
sendHTTP(api, c.External, "DELETE", "/api/v0/blueprints/delete/test", ``) sendHTTP(api, c.External, "DELETE", "/api/v0/blueprints/delete/test", ``)
@ -355,8 +363,7 @@ func TestComposeQueue(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
s := store.New(nil) api, s := createWeldrAPI(rpmmd_mock.BaseFixture)
api := weldr.New(repo, packages, nil, s)
sendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`) sendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
// create job and leave it waiting // create job and leave it waiting
sendHTTP(api, false, "POST", "/api/v0/compose", `{"blueprint_name": "test","compose_type": "tar","branch": "master"}`) sendHTTP(api, false, "POST", "/api/v0/compose", `{"blueprint_name": "test","compose_type": "tar","branch": "master"}`)
@ -390,7 +397,7 @@ func TestSourcesNew(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON) testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
sendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``) sendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``)
} }
@ -409,7 +416,7 @@ func TestSourcesDelete(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
api := weldr.New(repo, packages, nil, store.New(nil)) api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
sendHTTP(api, true, "POST", "/api/v0/projects/source/new", `{"name": "fish","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`) sendHTTP(api, true, "POST", "/api/v0/projects/source/new", `{"name": "fish","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`)
testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON) testRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
sendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``) sendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``)