diff --git a/internal/provider/rpm/rpm_meta_test.go b/internal/provider/rpm/rpm_meta_test.go index 19d1a69..1aa3c2f 100644 --- a/internal/provider/rpm/rpm_meta_test.go +++ b/internal/provider/rpm/rpm_meta_test.go @@ -7,16 +7,14 @@ import ( "io" "net/http" "net/http/httptest" - "os" "strings" "testing" "git.unkin.net/unkin/artifactapi/internal/provider" + "git.unkin.net/unkin/artifactapi/internal/testsupport" "git.unkin.net/unkin/artifactapi/pkg/models" ) -const fixtureRPM = "testdata/e2e-testpkg-1.0-1.noarch.rpm" - type fakeBlobReader struct{ data []byte } func (f fakeBlobReader) Download(_ context.Context, _ string) (io.ReadCloser, int64, error) { @@ -82,10 +80,7 @@ func TestRPMValidateUpload(t *testing.T) { } func TestRPMAfterUpload(t *testing.T) { - data, err := os.ReadFile(fixtureRPM) - if err != nil { - t.Fatalf("fixture: %v", err) - } + data := testsupport.MinimalRPM("e2e-testpkg", "1.0", "1", "noarch") store := &fakeMetaStore{} (&Provider{}).AfterUpload(context.Background(), "myrepo", "Packages/e2e-testpkg-1.0-1.noarch.rpm", "sha256:deadbeef", fakeBlobReader{data: data}, store) diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 3b04165..62b7bcf 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -301,10 +301,7 @@ func TestServerLocalPyPI(t *testing.T) { func TestServerLocalRPMRepodata(t *testing.T) { requireStack(t) - rpm, err := os.ReadFile("../provider/rpm/testdata/e2e-testpkg-1.0-1.noarch.rpm") - if err != nil { - t.Skipf("rpm fixture unavailable: %v", err) - } + rpm := testsupport.MinimalRPM("e2e-testpkg", "1.0", "1", "noarch") if resp, b := req(t, "POST", "/api/v2/remotes", `{"name":"srv-rpm","package_type":"rpm","repo_type":"local"}`); resp.StatusCode != 201 { t.Fatalf("create rpm local: %d %s", resp.StatusCode, b) } diff --git a/internal/testsupport/rpm.go b/internal/testsupport/rpm.go new file mode 100644 index 0000000..49f37bb --- /dev/null +++ b/internal/testsupport/rpm.go @@ -0,0 +1,59 @@ +package testsupport + +import ( + "bytes" + "encoding/binary" +) + +// MinimalRPM builds a valid-enough RPM package in pure Go (no committed binary +// fixture, no external rpmbuild). It carries just the header tags the provider +// reads: name/version/release/arch plus a single self Provides entry, which is +// enough for cavaliergopher/rpm to parse and for repodata generation. +func MinimalRPM(name, version, release, arch string) []byte { + type tag struct { + id, typ, count uint32 + data []byte + } + cstr := func(s string) []byte { return append([]byte(s), 0) } + tags := []tag{ + {1000, 6, 1, cstr(name)}, // RPMTAG_NAME (STRING) + {1001, 6, 1, cstr(version)}, // RPMTAG_VERSION + {1002, 6, 1, cstr(release)}, // RPMTAG_RELEASE + {1022, 6, 1, cstr(arch)}, // RPMTAG_ARCH + {1047, 8, 1, cstr(name)}, // RPMTAG_PROVIDENAME (STRING_ARRAY) + {1112, 4, 1, []byte{0, 0, 0, 0}}, // RPMTAG_PROVIDEFLAGS (INT32) + {1113, 8, 1, cstr(version)}, // RPMTAG_PROVIDEVERSION (STRING_ARRAY) + } + + buildHeader := func(entries []tag) []byte { + var index, store bytes.Buffer + for _, e := range entries { + off := uint32(store.Len()) + for _, v := range []uint32{e.id, e.typ, off, e.count} { + binary.Write(&index, binary.BigEndian, v) + } + store.Write(e.data) + } + var b bytes.Buffer + b.Write([]byte{0x8e, 0xad, 0xe8, 0x01, 0, 0, 0, 0}) // header magic + reserved + binary.Write(&b, binary.BigEndian, uint32(len(entries))) + binary.Write(&b, binary.BigEndian, uint32(store.Len())) + b.Write(index.Bytes()) + b.Write(store.Bytes()) + return b.Bytes() + } + + lead := make([]byte, 96) + copy(lead[0:4], []byte{0xed, 0xab, 0xee, 0xdb}) // lead magic + lead[4] = 3 // major version + binary.BigEndian.PutUint16(lead[8:10], 1) // archnum + copy(lead[10:76], name) // name (66 bytes, null-padded) + binary.BigEndian.PutUint16(lead[76:78], 1) // osnum + binary.BigEndian.PutUint16(lead[78:80], 5) // signature type + + var out bytes.Buffer + out.Write(lead) + out.Write(buildHeader(nil)) // empty signature header (16 bytes, 8-aligned) + out.Write(buildHeader(tags)) + return out.Bytes() +}