Add dnfjson mock data package and cmd
The cases are directly copied (or lightly adapted) from rpmmd_mock/fixtures. The purpose of the mocks/dnfjson package is to create files with data for testing the dnfjson package without the need to call the dnf-json script. Each public function creates a file with test responses in the same format as the dnf-json script's responses (either valid results or errors). The dnfjson.Solver can be configured to call the new ./cmd/mock-dnf-json program with the test data file as an argument and a valid dnf-json request for input. The mock-dnf-json checks the input request for unknown fields before responding with the contents of the file. Each test case file contains two responses, one for each command supported by dnf-json: "depsolve" and "dump". mock-dnf-json responds with the appropriate data based on the command in the request. This is necessary for tests that require both commands in the same call, e.g., tests of the weldr API's moduleInfoHandler() which fetches a package list and then needs to depsolve a subset of those packages. There are also cases when we want one of the two responses to be an error. The mock-dnf-json program will return with an error code if it can successfully unmarshal the intended response into the dnfjson.Error type.
This commit is contained in:
parent
82007dcf46
commit
e9a7a50496
2 changed files with 296 additions and 0 deletions
93
cmd/mock-dnf-json/dnf-json.go
Normal file
93
cmd/mock-dnf-json/dnf-json.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Mock dnf-json
|
||||
//
|
||||
// The purpose of this program is to return fake but expected responses to
|
||||
// dnf-json depsolve and dump queries. Tests should initialise a
|
||||
// dnfjson.Solver and configure it to run this program via the SetDNFJSONPath()
|
||||
// method. This utility accepts queries and returns responses with the same
|
||||
// structure as the dnf-json Python script.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/dnfjson"
|
||||
)
|
||||
|
||||
func maybeFail(err error) {
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
}
|
||||
|
||||
func fail(err error) {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func readRequest(r io.Reader) dnfjson.Request {
|
||||
j := json.NewDecoder(os.Stdin)
|
||||
j.DisallowUnknownFields()
|
||||
|
||||
var req dnfjson.Request
|
||||
err := j.Decode(&req)
|
||||
maybeFail(err)
|
||||
return req
|
||||
}
|
||||
|
||||
func readTestCase() string {
|
||||
if len(os.Args) < 2 {
|
||||
fail(errors.New("no test case specified"))
|
||||
}
|
||||
if len(os.Args) > 2 {
|
||||
fail(errors.New("invalid number of arguments: you must specify a test case"))
|
||||
}
|
||||
return os.Args[1]
|
||||
}
|
||||
|
||||
func parseResponse(resp []byte, command string) json.RawMessage {
|
||||
parsedResponse := make(map[string]json.RawMessage)
|
||||
err := json.Unmarshal(resp, &parsedResponse)
|
||||
maybeFail(err)
|
||||
if command == "chain-depsolve" {
|
||||
// treat chain-depsolve and depsolve the same
|
||||
command = "depsolve"
|
||||
}
|
||||
return parsedResponse[command]
|
||||
}
|
||||
|
||||
func checkForError(msg json.RawMessage) bool {
|
||||
j := json.NewDecoder(bytes.NewReader(msg))
|
||||
j.DisallowUnknownFields()
|
||||
dnferror := new(dnfjson.Error)
|
||||
err := j.Decode(dnferror)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
testFilePath := readTestCase()
|
||||
|
||||
req := readRequest(os.Stdin)
|
||||
|
||||
testFile, err := os.Open(testFilePath)
|
||||
if err != nil {
|
||||
fail(fmt.Errorf("failed to open test file %q\n", testFilePath))
|
||||
}
|
||||
defer testFile.Close()
|
||||
response, err := io.ReadAll(testFile)
|
||||
if err != nil {
|
||||
fail(fmt.Errorf("failed to read test file %q\n", testFilePath))
|
||||
}
|
||||
|
||||
res := parseResponse(response, req.Command)
|
||||
fmt.Print(string(parseResponse(response, req.Command)))
|
||||
|
||||
// check if we should return with error
|
||||
if checkForError(res) {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
203
internal/mocks/dnfjson/dnfjson.go
Normal file
203
internal/mocks/dnfjson/dnfjson.go
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
// dnfjson_mock provides data and methods for testing the dnfjson package.
|
||||
package dnfjson_mock
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/dnfjson"
|
||||
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
||||
)
|
||||
|
||||
func generatePackageList() rpmmd.PackageList {
|
||||
baseTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var packageList rpmmd.PackageList
|
||||
|
||||
for i := 0; i < 22; i++ {
|
||||
basePackage := rpmmd.Package{
|
||||
Name: fmt.Sprintf("package%d", i),
|
||||
Summary: fmt.Sprintf("pkg%d sum", i),
|
||||
Description: fmt.Sprintf("pkg%d desc", i),
|
||||
URL: fmt.Sprintf("https://pkg%d.example.com", i),
|
||||
Epoch: 0,
|
||||
Version: fmt.Sprintf("%d.0", i),
|
||||
Release: fmt.Sprintf("%d.fc30", i),
|
||||
Arch: "x86_64",
|
||||
BuildTime: baseTime.AddDate(0, i, 0),
|
||||
License: "MIT",
|
||||
}
|
||||
|
||||
secondBuild := basePackage
|
||||
|
||||
secondBuild.Version = fmt.Sprintf("%d.1", i)
|
||||
secondBuild.BuildTime = basePackage.BuildTime.AddDate(0, 0, 1)
|
||||
|
||||
packageList = append(packageList, basePackage, secondBuild)
|
||||
}
|
||||
|
||||
return packageList
|
||||
}
|
||||
|
||||
func createBaseDepsolveFixture() []dnfjson.PackageSpec {
|
||||
return []dnfjson.PackageSpec{
|
||||
{
|
||||
Name: "dep-package3",
|
||||
Epoch: 7,
|
||||
Version: "3.0.3",
|
||||
Release: "1.fc30",
|
||||
Arch: "x86_64",
|
||||
RepoID: "0",
|
||||
},
|
||||
{
|
||||
Name: "dep-package1",
|
||||
Epoch: 0,
|
||||
Version: "1.33",
|
||||
Release: "2.fc30",
|
||||
Arch: "x86_64",
|
||||
RepoID: "0",
|
||||
},
|
||||
{
|
||||
Name: "dep-package2",
|
||||
Epoch: 0,
|
||||
Version: "2.9",
|
||||
Release: "1.fc30",
|
||||
Arch: "x86_64",
|
||||
RepoID: "0",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BaseDeps is the expected list of dependencies (as rpmmd.PackageSpec) from
|
||||
// the Base ResponseGenerator
|
||||
func BaseDeps() []rpmmd.PackageSpec {
|
||||
return []rpmmd.PackageSpec{
|
||||
{
|
||||
Name: "dep-package3",
|
||||
Epoch: 7,
|
||||
Version: "3.0.3",
|
||||
Release: "1.fc30",
|
||||
Arch: "x86_64",
|
||||
CheckGPG: true,
|
||||
},
|
||||
{
|
||||
Name: "dep-package1",
|
||||
Epoch: 0,
|
||||
Version: "1.33",
|
||||
Release: "2.fc30",
|
||||
Arch: "x86_64",
|
||||
CheckGPG: true,
|
||||
},
|
||||
{
|
||||
Name: "dep-package2",
|
||||
Epoch: 0,
|
||||
Version: "2.9",
|
||||
Release: "1.fc30",
|
||||
Arch: "x86_64",
|
||||
CheckGPG: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ResponseGenerator func(string) string
|
||||
|
||||
func Base(tmpdir string) string {
|
||||
deps := map[string]interface{}{
|
||||
"checksums": map[string]string{
|
||||
"0": "test:responsechecksum",
|
||||
},
|
||||
"dependencies": createBaseDepsolveFixture(),
|
||||
}
|
||||
|
||||
pkgs := map[string]interface{}{
|
||||
"checksums": map[string]string{
|
||||
"0": "test:responsechecksum",
|
||||
},
|
||||
"packages": generatePackageList(),
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"depsolve": deps,
|
||||
"dump": pkgs,
|
||||
}
|
||||
path := filepath.Join(tmpdir, "base.json")
|
||||
write(data, path)
|
||||
return path
|
||||
}
|
||||
|
||||
func NonExistingPackage(tmpdir string) string {
|
||||
deps := dnfjson.Error{
|
||||
Kind: "MarkingErrors",
|
||||
Reason: "Error occurred when marking packages for installation: Problems in request:\nmissing packages: fash",
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"depsolve": deps,
|
||||
}
|
||||
path := filepath.Join(tmpdir, "notexist.json")
|
||||
write(data, path)
|
||||
return path
|
||||
}
|
||||
|
||||
func BadDepsolve(tmpdir string) string {
|
||||
deps := dnfjson.Error{
|
||||
Kind: "DepsolveError",
|
||||
Reason: "There was a problem depsolving ['go2rpm']: \n Problem: conflicting requests\n - nothing provides askalono-cli needed by go2rpm-1-4.fc31.noarch",
|
||||
}
|
||||
pkgs := map[string]interface{}{
|
||||
"checksums": map[string]string{
|
||||
"0": "test:responsechecksum",
|
||||
},
|
||||
"packages": generatePackageList(),
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"depsolve": deps,
|
||||
"dump": pkgs,
|
||||
}
|
||||
path := filepath.Join(tmpdir, "baddepsolve.json")
|
||||
write(data, path)
|
||||
return path
|
||||
}
|
||||
|
||||
func BadFetch(tmpdir string) string {
|
||||
deps := dnfjson.Error{
|
||||
Kind: "DepsolveError",
|
||||
Reason: "There was a problem depsolving ['go2rpm']: \n Problem: conflicting requests\n - nothing provides askalono-cli needed by go2rpm-1-4.fc31.noarch",
|
||||
}
|
||||
pkgs := dnfjson.Error{
|
||||
Kind: "FetchError",
|
||||
Reason: "There was a problem when fetching packages.",
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"depsolve": deps,
|
||||
"dump": pkgs,
|
||||
}
|
||||
path := filepath.Join(tmpdir, "badfetch.json")
|
||||
write(data, path)
|
||||
return path
|
||||
}
|
||||
|
||||
func marshal(data interface{}) []byte {
|
||||
jdata, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jdata
|
||||
}
|
||||
|
||||
func write(data interface{}, path string) {
|
||||
fp, err := os.Create(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err := fp.Write(marshal(data)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue