Test the indexer's manifests, the new fields of modules and candidates, and the fpset (issue 1357)
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The values below that today's SDK wrote were produced by EncodeCandidate(sampleCandidate()) and EncodeModule at
|
||||
// mesh-sdk 5786ba19 (source_test.go's samples), and are pasted as the bytes it wrote.
|
||||
const (
|
||||
oldCandidateJSON = `{"v":1,"group":"feat/x","base":12,"heads":"4b5491557853","pulls":[{"repository":"novox/mesh-sdk","number":36,"head":"aaaa","modules":["mesh-mail"]},{"repository":"novox/mesh-control","number":7,"head":"bbbb","modules":["mesh-controller"]}]}`
|
||||
oldModuleJSON = `{"v":1,"fp":"src1:abc","src":5,"repository":"novox/mesh-sdk","commit":"cccc"}`
|
||||
oldNoneJSON = `{"v":1,"none":"a build that resolves packages when built","repository":"novox/mesh-sdk","commit":"cccc"}`
|
||||
)
|
||||
|
||||
// The reader structs of today's shape (source.go at 5786ba19): what the gate and the view are built on until they move
|
||||
// to this SDK. They decode with plain json.Unmarshal after the version check, as decode does.
|
||||
type oldModule struct {
|
||||
V int `json:"v"`
|
||||
FP string `json:"fp,omitempty"`
|
||||
None string `json:"none,omitempty"`
|
||||
Src uint64 `json:"src,omitempty"`
|
||||
Repository string `json:"repository"`
|
||||
Commit string `json:"commit"`
|
||||
}
|
||||
|
||||
type oldCandidate struct {
|
||||
V int `json:"v"`
|
||||
Group string `json:"group"`
|
||||
Base uint64 `json:"base"`
|
||||
HeadsHash string `json:"heads"`
|
||||
Pulls []struct {
|
||||
Head
|
||||
Modules []string `json:"modules"`
|
||||
} `json:"pulls"`
|
||||
}
|
||||
|
||||
func fp2of(c byte) string { return SourcePrefix2 + strings.Repeat(string(c), 64) }
|
||||
|
||||
func digestOf(c byte) string { return strings.Repeat(string(c), 64) }
|
||||
|
||||
func newCandidate() Candidate {
|
||||
c := sampleCandidate()
|
||||
c.Pulls[0].Fps = map[string]CandidateModule{"mesh-mail": {FP2: fp2of('a'), Manifest: digestOf('1')}}
|
||||
c.Pulls[1].Fps = map[string]CandidateModule{"mesh-controller": {None2: "no lock", Manifest: digestOf('2')}}
|
||||
c.FPSet = FingerprintSet(c.Pulls)
|
||||
return c
|
||||
}
|
||||
|
||||
// 1357/AC1: the digest is the hex sha256 of exactly the bytes; the key is that hex; a key that is not 64 lowercase hex
|
||||
// is refused.
|
||||
func TestManifestKeys(t *testing.T) {
|
||||
raw := []byte("{\n \"module\": \"x\"\n}\n")
|
||||
sum := sha256.Sum256(raw)
|
||||
want := hex.EncodeToString(sum[:])
|
||||
if got := ManifestDigest(raw); got != want {
|
||||
t.Errorf("ManifestDigest = %s, want %s", got, want)
|
||||
}
|
||||
key, err := ManifestsKey(want)
|
||||
if err != nil || key != want {
|
||||
t.Errorf("ManifestsKey = %q, %v", key, err)
|
||||
}
|
||||
back, err := ParseManifestsKey(key)
|
||||
if err != nil || back != want {
|
||||
t.Errorf("ParseManifestsKey = %q, %v", back, err)
|
||||
}
|
||||
for _, bad := range []string{"", "abc", strings.ToUpper(want), want + "0", want[:63], "sha256:" + want, strings.Repeat("g", 64)} {
|
||||
if _, err := ManifestsKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
|
||||
t.Errorf("ManifestsKey(%q) = %v, want a refusal naming it", bad, err)
|
||||
}
|
||||
if _, err := ParseManifestsKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
|
||||
t.Errorf("ParseManifestsKey(%q) = %v, want a refusal naming it", bad, err)
|
||||
}
|
||||
}
|
||||
if ManifestsBucket != "mesh-delivery-indexer_manifests" || ManifestsHistory != 1 {
|
||||
t.Errorf("bucket %s history %d", ManifestsBucket, ManifestsHistory)
|
||||
}
|
||||
}
|
||||
|
||||
// 1357/AC2 (the shape of it; the 21 KB case is in manifests_mailu_test.go): a manifests value encodes and decodes with
|
||||
// its manifest byte for byte, whatever its spacing, escapes and characters, and its digest is the key.
|
||||
func TestManifestRoundTripBytesForByte(t *testing.T) {
|
||||
// odd spacing, a tab, an escaped newline and quote, an em dash and an HTML-looking character, a trailing newline:
|
||||
// what a JSON re-encoder would change.
|
||||
raw := []byte("{\n\t\"module\": \"x\",\n \"why\": \"a — b <c> & \\n \\\" d é\",\n\n \"list\": [ \"a\",\"b\" ]\n}\n\n")
|
||||
m := Manifest{Module: "x", JSON: string(raw)}
|
||||
data, err := EncodeManifest(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := DecodeManifest(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.JSON != string(raw) || ManifestDigest([]byte(got.JSON)) != ManifestDigest(raw) || got.Module != "x" || got.V != Version {
|
||||
t.Errorf("the manifest changed on the way: %q", got.JSON)
|
||||
}
|
||||
for name, bad := range map[string]Manifest{
|
||||
"no module": {JSON: string(raw)},
|
||||
"a dotted module": {Module: "a.b", JSON: string(raw)},
|
||||
"no manifest": {Module: "x"},
|
||||
"not JSON": {Module: "x", JSON: "{nope"},
|
||||
"not UTF-8": {Module: "x", JSON: "{\"a\":\"\xff\"}"},
|
||||
} {
|
||||
if _, err := EncodeManifest(bad); err == nil {
|
||||
t.Errorf("a manifest with %s was encoded", name)
|
||||
}
|
||||
}
|
||||
if _, err := DecodeManifest([]byte(`{"v":2,"module":"m","manifest":"{}"}`)); !errors.Is(err, ErrUnknownVersion) {
|
||||
t.Errorf("version 2: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1357/AC3: a Module and a Candidate written by today's SDK decode and check under the new one, new fields empty.
|
||||
func TestOldValuesDecodeUnderTheNewSDK(t *testing.T) {
|
||||
c, err := DecodeCandidate([]byte(oldCandidateJSON))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.FPSet != "" || c.Pulls[0].Fps != nil || c.Pulls[1].Fps != nil || c.Group != "feat/x" {
|
||||
t.Errorf("a candidate of today grew fields: %+v", c)
|
||||
}
|
||||
for _, in := range []string{oldModuleJSON, oldNoneJSON} {
|
||||
m, err := DecodeModule([]byte(in))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.FP2 != "" || m.None2 != "" || m.Manifest != "" {
|
||||
t.Errorf("a module of today grew fields: %+v", m)
|
||||
}
|
||||
}
|
||||
// And the new SDK writes today's bytes for today's value: nothing new appears when nothing new is set.
|
||||
data, err := EncodeCandidate(sampleCandidate())
|
||||
if err != nil || string(data) != oldCandidateJSON {
|
||||
t.Errorf("EncodeCandidate of an old value = %s, %v", data, err)
|
||||
}
|
||||
data, err = EncodeModule(Module{FP: "src1:abc", Src: 5, Repository: "novox/mesh-sdk", Commit: "cccc"})
|
||||
if err != nil || string(data) != oldModuleJSON {
|
||||
t.Errorf("EncodeModule of an old value = %s, %v", data, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1357/AC4: a value with the new fields decodes, under a reader struct of today's shape, with no error.
|
||||
func TestNewValuesDecodeUnderTheOldReader(t *testing.T) {
|
||||
m := Module{FP: "src1:abc", FP2: fp2of('b'), Manifest: digestOf('3'), Src: 5, Repository: "novox/mesh-sdk", Commit: "cccc"}
|
||||
data, err := EncodeModule(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(data), `"fp2"`) || !strings.Contains(string(data), `"manifest"`) {
|
||||
t.Fatalf("the new fields are not in %s", data)
|
||||
}
|
||||
if err := checkVersion(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var om oldModule
|
||||
if err := json.Unmarshal(data, &om); err != nil || om.FP != "src1:abc" || om.Commit != "cccc" {
|
||||
t.Errorf("the old reader of a module: %+v %v", om, err)
|
||||
}
|
||||
data, err = EncodeCandidate(newCandidate())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(data), `"fpset"`) || !strings.Contains(string(data), `"fp2"`) {
|
||||
t.Fatalf("the new fields are not in %s", data)
|
||||
}
|
||||
var oc oldCandidate
|
||||
if err := json.Unmarshal(data, &oc); err != nil || oc.Group != "feat/x" || len(oc.Pulls) != 2 || oc.Pulls[0].Modules[0] != "mesh-mail" {
|
||||
t.Errorf("the old reader of a candidate: %+v %v", oc, err)
|
||||
}
|
||||
// and the new reader reads it back whole
|
||||
got, err := DecodeCandidate(data)
|
||||
want := newCandidate()
|
||||
want.V = Version
|
||||
if err != nil || !SameCandidate(got, want) {
|
||||
t.Errorf("round trip: %+v %v", got, err)
|
||||
}
|
||||
gm, err := DecodeModule(mustJSON(t, m))
|
||||
m.V = Version
|
||||
if err != nil || !SameModule(gm, m) {
|
||||
t.Errorf("module round trip: %+v %v", gm, err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustJSON(t *testing.T, m Module) []byte {
|
||||
t.Helper()
|
||||
data, err := EncodeModule(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 1357/AC5: a Module with an fp2 not of the new form's prefix, or with both fp2 and none2, is refused naming it; a
|
||||
// Candidate whose fpset is not the hash of its fingerprints is refused.
|
||||
func TestNewFieldsAreChecked(t *testing.T) {
|
||||
wantErr := func(name string, err error, words ...string) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Errorf("%s accepted", name)
|
||||
return
|
||||
}
|
||||
for _, w := range words {
|
||||
if !strings.Contains(err.Error(), w) {
|
||||
t.Errorf("%s: error %q does not name %q", name, err, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
base := Module{FP: "src1:abc", Repository: "a/b", Commit: "c"}
|
||||
m := base
|
||||
m.FP2 = "src1:" + strings.Repeat("a", 64)
|
||||
_, err := EncodeModule(m)
|
||||
wantErr("fp2 of the old form", err, "src1:"+strings.Repeat("a", 64))
|
||||
m.FP2 = "src2:xyz"
|
||||
_, err = EncodeModule(m)
|
||||
wantErr("fp2 malformed", err, "src2:xyz")
|
||||
m = base
|
||||
m.FP2, m.None2 = fp2of('a'), "no lock"
|
||||
_, err = EncodeModule(m)
|
||||
wantErr("fp2 and none2", err, "both", "fp2", "none2")
|
||||
m = base
|
||||
m.Manifest = "abc"
|
||||
_, err = EncodeModule(m)
|
||||
wantErr("a manifest digest malformed", err, "abc")
|
||||
m = base
|
||||
m.None2 = "no lock"
|
||||
if _, err = EncodeModule(m); err != nil {
|
||||
t.Errorf("none2 alone refused: %v", err)
|
||||
}
|
||||
// decode refuses what encode refuses
|
||||
_, err = DecodeModule([]byte(`{"v":1,"fp":"src1:abc","fp2":"src2:zz","repository":"a/b","commit":"c"}`))
|
||||
wantErr("decoded fp2 malformed", err, "src2:zz")
|
||||
|
||||
c := newCandidate()
|
||||
c.FPSet = "000000000000"
|
||||
_, err = EncodeCandidate(c)
|
||||
wantErr("a stale fpset", err, "fpset", "000000000000")
|
||||
c = newCandidate()
|
||||
c.FPSet = ""
|
||||
_, err = EncodeCandidate(c)
|
||||
wantErr("fingerprints and no fpset", err, "fpset")
|
||||
c = sampleCandidate()
|
||||
c.FPSet = FingerprintSet(newCandidate().Pulls)
|
||||
_, err = EncodeCandidate(c)
|
||||
wantErr("an fpset and no fingerprints", err, "fpset")
|
||||
c = newCandidate()
|
||||
c.Pulls[0].Fps["mesh-mail"] = CandidateModule{FP2: "src1:abc"}
|
||||
c.FPSet = FingerprintSet(c.Pulls)
|
||||
_, err = EncodeCandidate(c)
|
||||
wantErr("a candidate's fp2 of the old form", err, "src1:abc")
|
||||
c = newCandidate()
|
||||
c.Pulls[0].Fps["mesh-mail"] = CandidateModule{FP2: fp2of('a'), None2: "x"}
|
||||
c.FPSet = FingerprintSet(c.Pulls)
|
||||
_, err = EncodeCandidate(c)
|
||||
wantErr("a candidate's module with both", err, "both", "mesh-mail")
|
||||
c = newCandidate()
|
||||
c.Pulls[0].Fps["other"] = CandidateModule{FP2: fp2of('a')}
|
||||
c.FPSet = FingerprintSet(c.Pulls)
|
||||
_, err = EncodeCandidate(c)
|
||||
wantErr("a fingerprint of a module the pull does not touch", err, "other")
|
||||
}
|
||||
|
||||
// The fpset is the same for any order and changes with any fingerprint or reason (research 063 section 01:
|
||||
// verdicts.<g>.check.<fpset> is keyed by it).
|
||||
func TestFingerprintSet(t *testing.T) {
|
||||
c := newCandidate()
|
||||
want := FingerprintSet(c.Pulls)
|
||||
if len(want) != 12 || strings.Trim(want, "0123456789abcdef") != "" {
|
||||
t.Errorf("fpset %q is not 12 lowercase hex digits", want)
|
||||
}
|
||||
c.Pulls[0], c.Pulls[1] = c.Pulls[1], c.Pulls[0]
|
||||
if got := FingerprintSet(c.Pulls); got != want {
|
||||
t.Errorf("the order of pull requests changes it: %s", got)
|
||||
}
|
||||
c = newCandidate()
|
||||
c.Pulls[0].Fps["mesh-mail"] = CandidateModule{FP2: fp2of('b')}
|
||||
if got := FingerprintSet(c.Pulls); got == want {
|
||||
t.Error("a changed fingerprint keeps it")
|
||||
}
|
||||
c = newCandidate()
|
||||
c.Pulls[1].Fps["mesh-controller"] = CandidateModule{None2: "no tree"}
|
||||
if got := FingerprintSet(c.Pulls); got == want {
|
||||
t.Error("a changed reason keeps it")
|
||||
}
|
||||
c = newCandidate()
|
||||
c.Pulls[0].Fps["mesh-mail"] = CandidateModule{FP2: fp2of('a'), Manifest: digestOf('9')}
|
||||
if got := FingerprintSet(c.Pulls); got != want {
|
||||
t.Error("a manifest digest, which the fingerprint already covers, changes it")
|
||||
}
|
||||
}
|
||||
|
||||
// 1357/AC6: both values keep version 1, and a value of another version is refused with ErrUnknownVersion.
|
||||
func TestVersionsStayOne(t *testing.T) {
|
||||
if Version != 1 {
|
||||
t.Fatalf("Version = %d", Version)
|
||||
}
|
||||
for _, data := range [][]byte{mustJSON(t, Module{FP: "src1:a", FP2: fp2of('a'), Repository: "a/b", Commit: "c"})} {
|
||||
if !strings.HasPrefix(string(data), `{"v":1,`) {
|
||||
t.Errorf("module at %s", data)
|
||||
}
|
||||
}
|
||||
data, _ := EncodeCandidate(newCandidate())
|
||||
if !strings.HasPrefix(string(data), `{"v":1,`) {
|
||||
t.Errorf("candidate at %s", data)
|
||||
}
|
||||
for _, in := range []string{
|
||||
`{"v":2,"fp":"src1:a","fp2":"` + fp2of('a') + `","repository":"a/b","commit":"c"}`,
|
||||
`{"fp2":"` + fp2of('a') + `","repository":"a/b","commit":"c"}`,
|
||||
} {
|
||||
if _, err := DecodeModule([]byte(in)); !errors.Is(err, ErrUnknownVersion) {
|
||||
t.Errorf("DecodeModule(%s) = %v", in, err)
|
||||
}
|
||||
}
|
||||
if _, err := DecodeCandidate([]byte(`{"v":2,"group":"g","fpset":"abc"}`)); !errors.Is(err, ErrUnknownVersion) {
|
||||
t.Errorf("candidate version 2: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user