|
| 1 | +package test |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | + "testing" |
| 6 | + |
| 7 | + "github.com/hdresearch/vers-cli/test/testutil" |
| 8 | +) |
| 9 | + |
| 10 | +// TestRepoLifecycle exercises the full repo CRUD lifecycle against production: |
| 11 | +// create repo → list → get → create tag → list tags → get tag → update tag → delete tag → delete repo. |
| 12 | +// Everything is cleaned up regardless of test outcome. |
| 13 | +func TestRepoLifecycle(t *testing.T) { |
| 14 | + testutil.TestEnv(t) |
| 15 | + testutil.EnsureBuilt(t) |
| 16 | + |
| 17 | + repoName := testutil.UniqueAlias("repo") |
| 18 | + |
| 19 | + // Always clean up the repo at end, even if something fails midway. |
| 20 | + t.Cleanup(func() { |
| 21 | + // Best-effort delete; ignore errors (repo may already be deleted). |
| 22 | + testutil.RunVers(t, testutil.DefaultTimeout, "repo", "delete", repoName) |
| 23 | + }) |
| 24 | + |
| 25 | + // ── Create repo ────────────────────────────────────────────── |
| 26 | + out, err := testutil.RunVers(t, testutil.DefaultTimeout, |
| 27 | + "repo", "create", repoName, "-d", "integration test repo") |
| 28 | + if err != nil { |
| 29 | + t.Fatalf("repo create failed: %v\nOutput:\n%s", err, out) |
| 30 | + } |
| 31 | + if !strings.Contains(out, repoName) { |
| 32 | + t.Fatalf("expected repo name in output, got:\n%s", out) |
| 33 | + } |
| 34 | + |
| 35 | + // ── List repos ─────────────────────────────────────────────── |
| 36 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, "repo", "list") |
| 37 | + if err != nil { |
| 38 | + t.Fatalf("repo list failed: %v\nOutput:\n%s", err, out) |
| 39 | + } |
| 40 | + if !strings.Contains(out, repoName) { |
| 41 | + t.Fatalf("expected %s in list output, got:\n%s", repoName, out) |
| 42 | + } |
| 43 | + |
| 44 | + // ── List repos (quiet) ─────────────────────────────────────── |
| 45 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, "repo", "list", "-q") |
| 46 | + if err != nil { |
| 47 | + t.Fatalf("repo list -q failed: %v\nOutput:\n%s", err, out) |
| 48 | + } |
| 49 | + found := false |
| 50 | + for _, line := range strings.Split(strings.TrimSpace(out), "\n") { |
| 51 | + if strings.TrimSpace(line) == repoName { |
| 52 | + found = true |
| 53 | + break |
| 54 | + } |
| 55 | + } |
| 56 | + if !found { |
| 57 | + t.Fatalf("expected %s in quiet list output, got:\n%s", repoName, out) |
| 58 | + } |
| 59 | + |
| 60 | + // ── List repos (json) ──────────────────────────────────────── |
| 61 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, "repo", "list", "--format", "json") |
| 62 | + if err != nil { |
| 63 | + t.Fatalf("repo list --format json failed: %v\nOutput:\n%s", err, out) |
| 64 | + } |
| 65 | + if !strings.Contains(out, repoName) { |
| 66 | + t.Fatalf("expected %s in json output, got:\n%s", repoName, out) |
| 67 | + } |
| 68 | + |
| 69 | + // ── Get repo ───────────────────────────────────────────────── |
| 70 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, "repo", "get", repoName) |
| 71 | + if err != nil { |
| 72 | + t.Fatalf("repo get failed: %v\nOutput:\n%s", err, out) |
| 73 | + } |
| 74 | + if !strings.Contains(out, repoName) { |
| 75 | + t.Fatalf("expected %s in get output, got:\n%s", repoName, out) |
| 76 | + } |
| 77 | + if !strings.Contains(out, "integration test repo") { |
| 78 | + t.Fatalf("expected description in get output, got:\n%s", out) |
| 79 | + } |
| 80 | + |
| 81 | + // ── We need a commit to create a tag. Create a VM, commit it, clean up. ── |
| 82 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, "run") |
| 83 | + if err != nil { |
| 84 | + t.Fatalf("vers run failed: %v\nOutput:\n%s", err, out) |
| 85 | + } |
| 86 | + vmID, err := testutil.ParseVMID(out) |
| 87 | + if err != nil { |
| 88 | + t.Fatalf("failed to parse VM ID: %v\nOutput:\n%s", err, out) |
| 89 | + } |
| 90 | + testutil.RegisterVMCleanup(t, vmID, true) |
| 91 | + |
| 92 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, "commit", "create", vmID) |
| 93 | + if err != nil { |
| 94 | + t.Fatalf("commit create failed: %v\nOutput:\n%s", err, out) |
| 95 | + } |
| 96 | + commitID := parseCommitID(t, out) |
| 97 | + |
| 98 | + // ── Create tag in repo ─────────────────────────────────────── |
| 99 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, |
| 100 | + "repo", "tag", "create", repoName, "v1", commitID, "-d", "first release") |
| 101 | + if err != nil { |
| 102 | + t.Fatalf("repo tag create failed: %v\nOutput:\n%s", err, out) |
| 103 | + } |
| 104 | + if !strings.Contains(out, repoName+":v1") { |
| 105 | + t.Fatalf("expected reference %s:v1 in output, got:\n%s", repoName, out) |
| 106 | + } |
| 107 | + |
| 108 | + // ── List tags ──────────────────────────────────────────────── |
| 109 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, |
| 110 | + "repo", "tag", "list", repoName) |
| 111 | + if err != nil { |
| 112 | + t.Fatalf("repo tag list failed: %v\nOutput:\n%s", err, out) |
| 113 | + } |
| 114 | + if !strings.Contains(out, "v1") { |
| 115 | + t.Fatalf("expected v1 in tag list output, got:\n%s", out) |
| 116 | + } |
| 117 | + |
| 118 | + // ── List tags (quiet) ──────────────────────────────────────── |
| 119 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, |
| 120 | + "repo", "tag", "list", repoName, "-q") |
| 121 | + if err != nil { |
| 122 | + t.Fatalf("repo tag list -q failed: %v\nOutput:\n%s", err, out) |
| 123 | + } |
| 124 | + if strings.TrimSpace(out) != "v1" { |
| 125 | + t.Fatalf("expected 'v1' in quiet tag list, got:\n%s", out) |
| 126 | + } |
| 127 | + |
| 128 | + // ── Get tag ────────────────────────────────────────────────── |
| 129 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, |
| 130 | + "repo", "tag", "get", repoName, "v1") |
| 131 | + if err != nil { |
| 132 | + t.Fatalf("repo tag get failed: %v\nOutput:\n%s", err, out) |
| 133 | + } |
| 134 | + if !strings.Contains(out, commitID) { |
| 135 | + t.Fatalf("expected commit ID in tag get output, got:\n%s", out) |
| 136 | + } |
| 137 | + if !strings.Contains(out, "first release") { |
| 138 | + t.Fatalf("expected description in tag get output, got:\n%s", out) |
| 139 | + } |
| 140 | + |
| 141 | + // ── Update tag description ─────────────────────────────────── |
| 142 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, |
| 143 | + "repo", "tag", "update", repoName, "v1", "-d", "updated release") |
| 144 | + if err != nil { |
| 145 | + t.Fatalf("repo tag update failed: %v\nOutput:\n%s", err, out) |
| 146 | + } |
| 147 | + |
| 148 | + // ── Delete tag ─────────────────────────────────────────────── |
| 149 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, |
| 150 | + "repo", "tag", "delete", repoName, "v1") |
| 151 | + if err != nil { |
| 152 | + t.Fatalf("repo tag delete failed: %v\nOutput:\n%s", err, out) |
| 153 | + } |
| 154 | + if !strings.Contains(out, "deleted") { |
| 155 | + t.Fatalf("expected 'deleted' in output, got:\n%s", out) |
| 156 | + } |
| 157 | + |
| 158 | + // ── Verify tag is gone ─────────────────────────────────────── |
| 159 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, |
| 160 | + "repo", "tag", "list", repoName) |
| 161 | + if err != nil { |
| 162 | + t.Fatalf("repo tag list after delete failed: %v\nOutput:\n%s", err, out) |
| 163 | + } |
| 164 | + if strings.Contains(out, "v1") && !strings.Contains(out, "No tags found") { |
| 165 | + t.Fatalf("expected v1 to be gone from tag list, got:\n%s", out) |
| 166 | + } |
| 167 | + |
| 168 | + // ── Delete repo ────────────────────────────────────────────── |
| 169 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, "repo", "delete", repoName) |
| 170 | + if err != nil { |
| 171 | + t.Fatalf("repo delete failed: %v\nOutput:\n%s", err, out) |
| 172 | + } |
| 173 | + if !strings.Contains(out, "deleted") { |
| 174 | + t.Fatalf("expected 'deleted' in output, got:\n%s", out) |
| 175 | + } |
| 176 | + |
| 177 | + // ── Verify repo is gone ────────────────────────────────────── |
| 178 | + out, err = testutil.RunVers(t, testutil.DefaultTimeout, "repo", "list", "-q") |
| 179 | + if err != nil { |
| 180 | + t.Fatalf("repo list after delete failed: %v\nOutput:\n%s", err, out) |
| 181 | + } |
| 182 | + if strings.Contains(out, repoName) { |
| 183 | + t.Fatalf("expected %s to be gone from list, got:\n%s", repoName, out) |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +// parseCommitID extracts a commit ID from `vers commit create` output. |
| 188 | +// Expected format: |
| 189 | +// |
| 190 | +// ✓ Committed VM '<vm-id>' |
| 191 | +// Commit ID: <commit-id> |
| 192 | +func parseCommitID(t *testing.T, output string) string { |
| 193 | + t.Helper() |
| 194 | + for _, line := range strings.Split(output, "\n") { |
| 195 | + line = strings.TrimSpace(line) |
| 196 | + if strings.HasPrefix(line, "Commit ID:") { |
| 197 | + parts := strings.SplitN(line, ":", 2) |
| 198 | + if len(parts) == 2 { |
| 199 | + id := strings.TrimSpace(parts[1]) |
| 200 | + if id != "" { |
| 201 | + return id |
| 202 | + } |
| 203 | + } |
| 204 | + } |
| 205 | + // Fallback: a bare UUID on its own line |
| 206 | + if len(line) == 36 && strings.Count(line, "-") == 4 { |
| 207 | + return line |
| 208 | + } |
| 209 | + } |
| 210 | + t.Fatalf("could not parse commit ID from output:\n%s", output) |
| 211 | + return "" |
| 212 | +} |
0 commit comments