133 lines
5.5 KiB
Go
133 lines
5.5 KiB
Go
package apply
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
func referenceTo(t *testing.T, path string, body []byte) *declaration.File {
|
|
t.Helper()
|
|
return &declaration.File{ID: "m.big", Type: declaration.TypeFile, Path: path,
|
|
Digest: declaration.DigestOf(body), Size: int64(len(body))}
|
|
}
|
|
|
|
func objects(m map[string][]byte, asked *[]string) FetchObject {
|
|
return func(_ context.Context, name string, limit int64) ([]byte, error) {
|
|
*asked = append(*asked, name)
|
|
b, ok := m[name]
|
|
if !ok {
|
|
return nil, errors.New("object not found")
|
|
}
|
|
return b, nil
|
|
}
|
|
}
|
|
|
|
func objectOf(body []byte) string {
|
|
name, _ := declaration.ObjectName(declaration.DigestOf(body))
|
|
return name
|
|
}
|
|
|
|
// 1695/AC1: both forms apply: a file by reference becomes the inline file, an inline one is left as it is.
|
|
func TestAFileByReferenceBecomesTheInlineFile(t *testing.T) {
|
|
body := []byte(strings.Repeat("a line\n", 20000))
|
|
inline := &declaration.File{ID: "m.small", Type: declaration.TypeFile, Path: "/etc/small", Content: "x"}
|
|
d := &declaration.Declaration{Resources: []declaration.Resource{inline, referenceTo(t, "/nowhere/big", body)}}
|
|
var asked []string
|
|
if err := ResolveFileReferences(context.Background(), d, objects(map[string][]byte{objectOf(body): body}, &asked)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := d.Resources[1].(*declaration.File)
|
|
if got.IsReference() || got.Content != string(body) || got.Path != "/nowhere/big" || d.Resources[0] != declaration.Resource(inline) {
|
|
t.Errorf("1695/AC1: resolved to %+v", got)
|
|
}
|
|
if len(asked) != 1 || asked[0] != objectOf(body) {
|
|
t.Errorf("1695/AC1: asked %v", asked)
|
|
}
|
|
}
|
|
|
|
// 1695/AC2: an object that differs by one byte, or in size, fails the whole resolve and gives no content.
|
|
func TestAnObjectThatDiffersFailsTheApply(t *testing.T) {
|
|
body := []byte(strings.Repeat("x", 70000))
|
|
bad := append([]byte(nil), body...)
|
|
bad[3] ^= 1
|
|
for name, served := range map[string][]byte{"one byte": bad, "shorter": body[1:]} {
|
|
d := &declaration.Declaration{Resources: []declaration.Resource{referenceTo(t, "/nowhere/big", body)}}
|
|
var asked []string
|
|
err := ResolveFileReferences(context.Background(), d, objects(map[string][]byte{objectOf(body): served}, &asked))
|
|
if err == nil || !strings.Contains(err.Error(), "nothing was applied") {
|
|
t.Errorf("1695/AC2: %s: %v", name, err)
|
|
}
|
|
if !d.Resources[0].(*declaration.File).IsReference() {
|
|
t.Errorf("1695/AC2: %s: the reference was replaced by unverified content", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1695/AC3: a missing object, or a node with no bus, fails the apply naming the file.
|
|
func TestAMissingObjectFailsTheApply(t *testing.T) {
|
|
body := []byte(strings.Repeat("y", 70000))
|
|
d := &declaration.Declaration{Resources: []declaration.Resource{referenceTo(t, "/nowhere/big", body)}}
|
|
var asked []string
|
|
if err := ResolveFileReferences(context.Background(), d, objects(nil, &asked)); err == nil ||
|
|
!strings.Contains(err.Error(), "m.big") || !strings.Contains(err.Error(), "object bucket") {
|
|
t.Errorf("1695/AC3: missing object: %v", err)
|
|
}
|
|
if err := ResolveFileReferences(context.Background(), d, nil); err == nil || !strings.Contains(err.Error(), "no link to the bus") {
|
|
t.Errorf("1695/AC3: no bus: %v", err)
|
|
}
|
|
}
|
|
|
|
// 1695/AC4: a file already on the machine that verifies is used and nothing is fetched; one that does not is fetched.
|
|
func TestAVerifiedFileOnTheMachineFetchesNothing(t *testing.T) {
|
|
body := []byte(strings.Repeat("z", 70000))
|
|
path := filepath.Join(t.TempDir(), "big")
|
|
if err := os.WriteFile(path, body, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var asked []string
|
|
d := &declaration.Declaration{Resources: []declaration.Resource{referenceTo(t, path, body)}}
|
|
if err := ResolveFileReferences(context.Background(), d, objects(nil, &asked)); err != nil || len(asked) != 0 {
|
|
t.Errorf("1695/AC4: err %v, asked %v", err, asked)
|
|
}
|
|
if err := os.WriteFile(path, body[:100], 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d = &declaration.Declaration{Resources: []declaration.Resource{referenceTo(t, path, body)}}
|
|
if err := ResolveFileReferences(context.Background(), d, objects(map[string][]byte{objectOf(body): body}, &asked)); err != nil || len(asked) != 1 {
|
|
t.Errorf("1695/AC4: a differing file: err %v, asked %v", err, asked)
|
|
}
|
|
}
|
|
|
|
// 1695/AC5: binary content comes back as bytes, and two files naming one digest fetch it once.
|
|
func TestBinaryContentAndOneFetchPerDigest(t *testing.T) {
|
|
body := append([]byte{0xff, 0xfe, 0x00}, []byte(strings.Repeat("b", 70000))...)
|
|
d := &declaration.Declaration{Resources: []declaration.Resource{referenceTo(t, "/n/a", body), referenceTo(t, "/n/b", body)}}
|
|
var asked []string
|
|
if err := ResolveFileReferences(context.Background(), d, objects(map[string][]byte{objectOf(body): body}, &asked)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a, _ := d.Resources[0].(*declaration.File).Body()
|
|
if d.Resources[0].(*declaration.File).Bytes == "" || string(a) != string(body) || len(asked) != 1 {
|
|
t.Errorf("1695/AC5: bytes %q asked %v", d.Resources[0].(*declaration.File).Bytes[:4], asked)
|
|
}
|
|
}
|
|
|
|
// 1695/AC9: an unresolved reference reaching the apply is refused, never written as an empty file.
|
|
func TestAnUnresolvedReferenceIsNeverWrittenEmpty(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "big")
|
|
f := referenceTo(t, path, []byte(strings.Repeat("q", 70000)))
|
|
if _, err := applyFile(f, store.Applied{}, nil, nil); err == nil || !strings.Contains(err.Error(), "not written empty") {
|
|
t.Errorf("1695/AC9: %v", err)
|
|
}
|
|
if _, err := os.Stat(path); err == nil {
|
|
t.Error("1695/AC9: a file was written")
|
|
}
|
|
}
|