test(integration): add checkpoint-backend matrix (I-1) · Entire

test(integration): add checkpoint-backend matrix (I-1)

d57ee8amain·

The integration suite had no git-refs coverage: every remote-touching test asserted the git-branch v1-branch topology only, while the e2e suite already ran both backends. This adds the missing axis.

TestEnv gains a CheckpointStore field injected as ENTIRE_CHECKPOINTS_PRIMARY into every spawned CLI and git hook (cliEnv + gitHookEnv), and propagated to clones. A ForEachBackend(t, fn) helper runs "git-branch"/"git-refs" subtests, plus backend-aware assertion helpers (CheckpointsPresentLocally/OnRemote, CheckpointExistsOnRemote, RemoteCheckpointState, LatestCheckpointID) mirroring e2e/testutil/backend.go so one test asserts against either topology.

Wires the pre-push and graceful-degradation tests in remote_operations_test.go, the HTTPS push/token tests in http_remote_test.go, and the explain fetch-on-miss test under both backends. Tests whose assertions are inherently v1-branch-shaped (commit counts/subjects, rebase parent counts, checkpoint_remote routing, treeless v1 clone, v1-ref-hash divergence) stay git-branch-only with an explicit rationale pointing at their git-refs follow-up in the test plan.

One genuine git-refs divergence surfaced and is flagged, not papered over: when a session stages its file changes after the stop hook, git-refs condensation creates no per-checkpoint ref (git-branch still creates the local v1 checkpoint). Skipped for git-refs with a KNOWN BUG note; a fresh repo and a stage-before-stop clone both create the ref correctly.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_012yi3hHGAGepwfrfjPETjGq

Sessions

f96568923ebdView transcript

Changes

5

//go:build integration

package integration

import (
    "os/exec"
    "sort"
    "strings"
    "testing"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/cmd/entire/cli/paths"
    "github.com/entireio/cli/cmd/entire/cli/testutil"
)

const (
    StoreGitBranch = "git-branch"
    StoreGitRefs   = "git-refs"

checkpointRefPrefix = "refs/entire/checkpoints/"
)

func ForEachBackend(t *testing.T, fn func(t *testing.T, backend string)) {
    t.Helper()
    for _, backend := range []string{StoreGitBranch, StoreGitRefs} {
        t.Run(backend, func(t *testing.T) {
            t.Parallel()
            fn(t, backend)
        })
    }
}

func (env *TestEnv) usingGitRefs() bool {
    return env.CheckpointStore == StoreGitRefs
}

func (env *TestEnv) LatestCheckpointID() string {
    env.T.Helper()
    if env.usingGitRefs() {
        return env.GetLatestCheckpointIDFromHistory()
    }
    return env.GetLatestCheckpointID()
}

func checkpointRefName(checkpointID string) string {
    return checkpointRefPrefix + id.CheckpointID(checkpointID).ShardFor() + "/" + checkpointID
}

func (env *TestEnv) CheckpointsPresentLocally() bool {
    env.T.Helper()
    if env.usingGitRefs() {
        return anyRefUnderPrefix(env.T, env.RepoDir, checkpointRefPrefix)
    }
    return env.BranchExists(paths.MetadataBranchName)
}

func (env *TestEnv) CheckpointsPresentOnRemote(bareDir string) bool {
    env.T.Helper()
    if env.usingGitRefs() {
        return anyRefUnderPrefix(env.T, bareDir, checkpointRefPrefix)
    }
    return env.BranchExistsOnRemote(bareDir, paths.MetadataBranchName)
}

func (env *TestEnv) CheckpointExistsOnRemote(bareDir, checkpointID string) bool {
    env.T.Helper()
    if env.usingGitRefs() {
        return refExists(env.T, bareDir, checkpointRefName(checkpointID))
    }
    return fileExistsOnRemoteBranch(env.T, bareDir, CheckpointSummaryPath(checkpointID))
}

func (env *TestEnv) RemoteCheckpointState(bareDir string) string {
    env.T.Helper()
    prefix := "refs/heads/" + paths.MetadataBranchName
    if env.usingGitRefs() {
        prefix = checkpointRefPrefix
    }
    cmd := exec.CommandContext(env.T.Context(), "git", "for-each-ref", "--format=%(refname) %(objectname)", prefix)
    cmd.Dir = bareDir
    cmd.Env = testutil.GitIsolatedEnv()
    out, err := cmd.Output()
    if err != nil {
        return ""
    }
    lines := strings.Split(strings.TrimSpace(string(out)), "\n")
    sort.Strings(lines)
    return strings.Join(lines, "\n")
}

func anyRefUnderPrefix(t *testing.T, dir, prefix string) bool {
    t.Helper()
    cmd := exec.CommandContext(t.Context(), "git", "for-each-ref", "--format=%(refname)", prefix)
    cmd.Dir = dir
    cmd.Env = testutil.GitIsolatedEnv()
    out, err := cmd.Output()
    if err != nil {
        return false
    }
    return strings.TrimSpace(string(out)) != ""
}

func refExists(t *testing.T, dir, ref string) bool {
    t.Helper()
    cmd := exec.CommandContext(t.Context(), "git", "show-ref", "--verify", "--quiet", ref)
    cmd.Dir = dir
    cmd.Env = testutil.GitIsolatedEnv()
    return cmd.Run() == nil
}

Changes