Step 32: Phase 5 polish.
E2e test covering targeting labels through push/pull cycle. Updated README with targeting docs and commands. All project docs updated. Phase 5 complete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
148
integration/phase5_test.go
Normal file
148
integration/phase5_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kisom/sgard/client"
|
||||
"github.com/kisom/sgard/garden"
|
||||
"github.com/kisom/sgard/server"
|
||||
"github.com/kisom/sgard/sgardpb"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
)
|
||||
|
||||
const bufSize = 1024 * 1024
|
||||
|
||||
// TestE2E_Phase5_Targeting verifies that targeting labels survive push/pull
|
||||
// and that restore respects them.
|
||||
func TestE2E_Phase5_Targeting(t *testing.T) {
|
||||
// Set up bufconn server.
|
||||
serverDir := t.TempDir()
|
||||
serverGarden, err := garden.Init(serverDir)
|
||||
if err != nil {
|
||||
t.Fatalf("init server: %v", err)
|
||||
}
|
||||
|
||||
lis := bufconn.Listen(bufSize)
|
||||
srv := grpc.NewServer()
|
||||
sgardpb.RegisterGardenSyncServer(srv, server.New(serverGarden))
|
||||
t.Cleanup(func() { srv.Stop() })
|
||||
go func() { _ = srv.Serve(lis) }()
|
||||
|
||||
conn, err := grpc.NewClient("passthrough:///bufconn",
|
||||
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return lis.Dial()
|
||||
}),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("dial: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = conn.Close() })
|
||||
|
||||
// --- Build source garden with targeted entries ---
|
||||
srcRoot := t.TempDir()
|
||||
srcRepoDir := filepath.Join(srcRoot, "repo")
|
||||
srcGarden, err := garden.Init(srcRepoDir)
|
||||
if err != nil {
|
||||
t.Fatalf("init source: %v", err)
|
||||
}
|
||||
|
||||
linuxFile := filepath.Join(srcRoot, "linux-only")
|
||||
everywhereFile := filepath.Join(srcRoot, "everywhere")
|
||||
neverArmFile := filepath.Join(srcRoot, "never-arm")
|
||||
|
||||
if err := os.WriteFile(linuxFile, []byte("linux"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(everywhereFile, []byte("everywhere"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(neverArmFile, []byte("not arm"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := srcGarden.Add([]string{linuxFile}, garden.AddOptions{Only: []string{"os:linux"}}); err != nil {
|
||||
t.Fatalf("Add linux-only: %v", err)
|
||||
}
|
||||
if err := srcGarden.Add([]string{everywhereFile}); err != nil {
|
||||
t.Fatalf("Add everywhere: %v", err)
|
||||
}
|
||||
if err := srcGarden.Add([]string{neverArmFile}, garden.AddOptions{Never: []string{"arch:arm64"}}); err != nil {
|
||||
t.Fatalf("Add never-arm: %v", err)
|
||||
}
|
||||
|
||||
// Bump timestamp.
|
||||
m := srcGarden.GetManifest()
|
||||
m.Updated = time.Now().UTC().Add(time.Hour)
|
||||
if err := srcGarden.ReplaceManifest(m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// --- Push ---
|
||||
ctx := context.Background()
|
||||
pushClient := client.New(conn)
|
||||
if _, err := pushClient.Push(ctx, srcGarden); err != nil {
|
||||
t.Fatalf("Push: %v", err)
|
||||
}
|
||||
|
||||
// --- Pull to fresh garden ---
|
||||
dstRoot := t.TempDir()
|
||||
dstRepoDir := filepath.Join(dstRoot, "repo")
|
||||
dstGarden, err := garden.Init(dstRepoDir)
|
||||
if err != nil {
|
||||
t.Fatalf("init dest: %v", err)
|
||||
}
|
||||
|
||||
pullClient := client.New(conn)
|
||||
if _, err := pullClient.Pull(ctx, dstGarden); err != nil {
|
||||
t.Fatalf("Pull: %v", err)
|
||||
}
|
||||
|
||||
// --- Verify targeting survived ---
|
||||
dm := dstGarden.GetManifest()
|
||||
if len(dm.Files) != 3 {
|
||||
t.Fatalf("expected 3 entries, got %d", len(dm.Files))
|
||||
}
|
||||
|
||||
for _, e := range dm.Files {
|
||||
switch {
|
||||
case e.Path == toTilde(linuxFile):
|
||||
if len(e.Only) != 1 || e.Only[0] != "os:linux" {
|
||||
t.Errorf("%s: only = %v, want [os:linux]", e.Path, e.Only)
|
||||
}
|
||||
case e.Path == toTilde(everywhereFile):
|
||||
if len(e.Only) != 0 || len(e.Never) != 0 {
|
||||
t.Errorf("%s: should have no targeting", e.Path)
|
||||
}
|
||||
case e.Path == toTilde(neverArmFile):
|
||||
if len(e.Never) != 1 || e.Never[0] != "arch:arm64" {
|
||||
t.Errorf("%s: never = %v, want [arch:arm64]", e.Path, e.Never)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify restore skips non-matching entries.
|
||||
// Delete all files, then restore — only matching entries should appear.
|
||||
_ = os.Remove(linuxFile)
|
||||
_ = os.Remove(everywhereFile)
|
||||
_ = os.Remove(neverArmFile)
|
||||
|
||||
if err := dstGarden.Restore(nil, true, nil); err != nil {
|
||||
t.Fatalf("Restore: %v", err)
|
||||
}
|
||||
|
||||
// "everywhere" should always be restored.
|
||||
if _, err := os.Stat(everywhereFile); os.IsNotExist(err) {
|
||||
t.Error("everywhere file should be restored")
|
||||
}
|
||||
|
||||
// "linux-only" depends on current OS — we just verify no error occurred.
|
||||
// "never-arm" depends on current arch.
|
||||
}
|
||||
Reference in New Issue
Block a user