check / check (push) Waiting to run
Nothing goes in the 1.0 manifest that 1.0 does not read or write, and mfer never wrote either field. mf.proto now reserves the numbers 301 and 303 and both names in MFFilePath, and mf.pb.go is regenerated. The decoded-size check counts only file entries, hashes and timestamps, export prints no ctime, and FileEntry loses the Ctime nothing set. FORMAT.md and the README no longer describe either field. The test of entries that decode too large gives each entry a second, empty hash: an entry with one hash and a modification time can no longer reach 8 times its size. Model: opus-5-5
194 lines
4.9 KiB
Go
194 lines
4.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"maps"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/mfer/mfer"
|
|
)
|
|
|
|
const testCmdExport = "export"
|
|
|
|
// buildTestManifest creates a manifest from in-memory files and returns its bytes.
|
|
func buildTestManifest(t *testing.T, files map[string][]byte) []byte {
|
|
t.Helper()
|
|
|
|
// The scan logs to the process-global logger, so serialize with the
|
|
// CLI runs.
|
|
runMu.Lock()
|
|
defer runMu.Unlock()
|
|
|
|
sourceFs := afero.NewMemMapFs()
|
|
for path, content := range files {
|
|
require.NoError(t, sourceFs.MkdirAll("/", 0o755))
|
|
require.NoError(t, afero.WriteFile(sourceFs, "/"+path, content, 0o644))
|
|
}
|
|
|
|
opts := &mfer.ScannerOptions{Fs: sourceFs}
|
|
s := mfer.NewScannerWithOptions(opts)
|
|
require.NoError(t, s.EnumerateFS(sourceFs, "/", nil))
|
|
|
|
var buf bytes.Buffer
|
|
|
|
require.NoError(t, s.ToManifest(context.Background(), &buf, nil))
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func TestExportManifestOperation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testFiles := map[string][]byte{
|
|
"hello.txt": []byte("Hello, World!"),
|
|
"sub/file.txt": []byte("nested content"),
|
|
}
|
|
manifestData := buildTestManifest(t, testFiles)
|
|
|
|
// Write manifest to memfs
|
|
fs := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fs, "/test.mf", manifestData, 0o644))
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
exitCode := runCLI(&RunOptions{
|
|
Appname: testApp,
|
|
Args: []string{testApp, testCmdExport, "/test.mf"},
|
|
Stdin: &bytes.Buffer{},
|
|
Stdout: &stdout,
|
|
Stderr: &stderr,
|
|
Fs: fs,
|
|
})
|
|
|
|
require.Equal(t, 0, exitCode, "stderr: %s", stderr.String())
|
|
|
|
var entries []ExportEntry
|
|
require.NoError(t, json.Unmarshal(stdout.Bytes(), &entries))
|
|
assert.Len(t, entries, 2)
|
|
|
|
// Verify entries have expected fields
|
|
pathSet := make(map[string]bool)
|
|
for _, e := range entries {
|
|
pathSet[e.Path] = true
|
|
assert.NotEmpty(t, e.Hashes, "entry %s should have hashes", e.Path)
|
|
assert.Positive(t, e.Size, "entry %s should have positive size", e.Path)
|
|
}
|
|
|
|
assert.True(t, pathSet["hello.txt"])
|
|
assert.True(t, pathSet["sub/file.txt"])
|
|
}
|
|
|
|
// export prints the fields of a file entry and nothing else, so no ctime.
|
|
func TestExportPrintsOnlyFileEntryFields(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
manifestData := buildTestManifest(t, map[string][]byte{"entry.txt": []byte("aaa")})
|
|
|
|
fs := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fs, "/test.mf", manifestData, 0o644))
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
exitCode := runCLI(&RunOptions{
|
|
Appname: testApp,
|
|
Args: []string{testApp, testCmdExport, "/test.mf"},
|
|
Stdin: &bytes.Buffer{},
|
|
Stdout: &stdout,
|
|
Stderr: &stderr,
|
|
Fs: fs,
|
|
})
|
|
|
|
require.Equal(t, 0, exitCode, "stderr: %s", stderr.String())
|
|
|
|
var entries []map[string]json.RawMessage
|
|
require.NoError(t, json.Unmarshal(stdout.Bytes(), &entries))
|
|
require.Len(t, entries, 1)
|
|
assert.Equal(t,
|
|
[]string{"hashes", "mode", "mtime", "path", "size"},
|
|
slices.Sorted(maps.Keys(entries[0])))
|
|
}
|
|
|
|
func TestExportFromHTTPURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testFiles := map[string][]byte{
|
|
"a.txt": []byte("aaa"),
|
|
}
|
|
manifestData := buildTestManifest(t, testFiles)
|
|
|
|
server := httptest.NewServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
_, _ = w.Write(manifestData)
|
|
}))
|
|
defer server.Close()
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
exitCode := runCLI(&RunOptions{
|
|
Appname: testApp,
|
|
Args: []string{testApp, testCmdExport, server.URL + "/index.mf"},
|
|
Stdin: &bytes.Buffer{},
|
|
Stdout: &stdout,
|
|
Stderr: &stderr,
|
|
Fs: afero.NewMemMapFs(),
|
|
})
|
|
|
|
require.Equal(t, 0, exitCode, "stderr: %s", stderr.String())
|
|
|
|
var entries []ExportEntry
|
|
require.NoError(t, json.Unmarshal(stdout.Bytes(), &entries))
|
|
assert.Len(t, entries, 1)
|
|
assert.Equal(t, "a.txt", entries[0].Path)
|
|
}
|
|
|
|
func TestListFromHTTPURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testFiles := map[string][]byte{
|
|
"one.txt": []byte("1"),
|
|
"two.txt": []byte("22"),
|
|
}
|
|
manifestData := buildTestManifest(t, testFiles)
|
|
|
|
server := httptest.NewServer(
|
|
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
_, _ = w.Write(manifestData)
|
|
}))
|
|
defer server.Close()
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
exitCode := runCLI(&RunOptions{
|
|
Appname: testApp,
|
|
Args: []string{testApp, cmdList, server.URL + "/index.mf"},
|
|
Stdin: &bytes.Buffer{},
|
|
Stdout: &stdout,
|
|
Stderr: &stderr,
|
|
Fs: afero.NewMemMapFs(),
|
|
})
|
|
|
|
require.Equal(t, 0, exitCode, "stderr: %s", stderr.String())
|
|
output := stdout.String()
|
|
assert.Contains(t, output, "one.txt")
|
|
assert.Contains(t, output, "two.txt")
|
|
}
|
|
|
|
func TestIsHTTPURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
assert.True(t, isHTTPURL("http://example.com/manifest.mf"))
|
|
assert.True(t, isHTTPURL("https://example.com/manifest.mf"))
|
|
assert.False(t, isHTTPURL("/local/path.mf"))
|
|
assert.False(t, isHTTPURL("relative/path.mf"))
|
|
assert.False(t, isHTTPURL("ftp://example.com/file"))
|
|
}
|