e2e/migrate_test.go
104 lines · 4727 bytes
1package e2e
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10func TestAccountMigration(t *testing.T) {
11 t.Parallel()
12 src := startInstance(t)
13 dst := startInstance(t)
14 key := src.newKey(t, "alice") // one identity, both instances
15 src.admin(t, "admin", "user", "create", "alice", "--key", key+".pub")
16 dst.admin(t, "admin", "user", "create", "alice", "--key", key+".pub")
17
18 // Source: profile, a repo with settings/topics, an issue thread, an MR.
19 if _, _, code := src.ssh(t, key, "", "profile", "set", "--description", "'tinkerer'", "--website", "https://alice.example"); code != 0 {
20 t.Fatal("profile set failed")
21 }
22 if _, _, code := src.ssh(t, key, "", "repo", "create", "alice/tool", "--description", "'a fine tool'"); code != 0 {
23 t.Fatal("repo create failed")
24 }
25 if _, _, code := src.ssh(t, key, "", "repo", "create", "alice/notes", "--private"); code != 0 {
26 t.Fatal("private repo create failed")
27 }
28 src.ssh(t, key, "", "repo", "topics", "add", "alice/tool", "go", "cli")
29 work := t.TempDir()
30 env := src.gitEnv(key)
31 mustGit(t, work, env, "clone", src.sshURL("alice/tool"), "w")
32 dir := filepath.Join(work, "w")
33 os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\n"), 0o644)
34 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
35 mustGit(t, dir, env, "add", ".")
36 mustGit(t, dir, env, "commit", "-q", "-m", "base")
37 mustGit(t, dir, env, "push", "-q", "origin", "main")
38 head := strings.TrimSpace(mustGit(t, dir, env, "rev-parse", "HEAD"))
39 src.ssh(t, key, "", "repo", "settings", "protect", "alice/tool", "main")
40 src.ssh(t, key, "", "repo", "settings", "require-signed", "alice/tool", "on")
41 if _, _, code := src.ssh(t, key, "", "issue", "create", "alice/tool", "--title", "'sharpen it'", "--body", "'too dull'"); code != 0 {
42 t.Fatal("issue create failed")
43 }
44 src.ssh(t, key, "", "issue", "label", "alice/tool", "1", "--add", "bug")
45 src.ssh(t, key, "", "issue", "comment", "alice/tool", "1", "--message", "'on it'")
46 src.ssh(t, key, "", "issue", "close", "alice/tool", "1")
47
48 // Export from the source, replay on the target.
49 bundleOut, errOut, code := src.ssh(t, key, "", "account", "export")
50 if code != 0 {
51 t.Fatalf("export: %s", errOut)
52 }
53 if strings.Contains(bundleOut, "ssh-ed25519") || strings.Contains(bundleOut, "PRIVATE") {
54 t.Fatal("bundle carries key material")
55 }
56 out, errOut, code := dst.ssh(t, key, bundleOut, "account", "import-bundle", "--source", "old.example")
57 if code != 0 {
58 t.Fatalf("import-bundle: %s", errOut)
59 }
60 if !strings.Contains(out, "imported 2 repos, 1 issues, 0 MRs, 1 comments") ||
61 !strings.Contains(out, "require-signed alice/tool on") ||
62 !strings.Contains(out, "protect alice/tool main") {
63 t.Fatalf("import summary: %s", out)
64 }
65
66 // Metadata arrived: issue with state, label, comment, attribution;
67 // topics, description, visibility.
68 out, _, _ = dst.ssh(t, key, "", "issue", "show", "alice/tool", "1", "--json")
69 if !strings.Contains(out, "sharpen it") || !strings.Contains(out, `"state":"closed"`) ||
70 !strings.Contains(out, `"labels":["bug"]`) || !strings.Contains(out, "on it") ||
71 !strings.Contains(out, "migrated issue old.example#1") {
72 t.Fatalf("migrated issue: %s", out)
73 }
74 out, _, _ = dst.ssh(t, key, "", "repo", "show", "alice/tool", "--json")
75 if !strings.Contains(out, `"topics":["cli","go"]`) || !strings.Contains(out, "a fine tool") {
76 t.Fatalf("migrated repo: %s", out)
77 }
78 out, _, _ = dst.ssh(t, key, "", "repo", "show", "alice/notes", "--json")
79 if !strings.Contains(out, `"visibility":"private"`) {
80 t.Fatalf("private visibility lost: %s", out)
81 }
82 // Deferred policies are NOT applied yet — the push must succeed.
83 out, _, _ = dst.ssh(t, key, "", "repo", "settings", "show", "alice/tool", "--json")
84 if strings.Contains(out, `"require_signed_commits":true`) || strings.Contains(out, "protected") {
85 t.Fatalf("push-blocking settings applied early: %s", out)
86 }
87
88 // Git data client-side: clone from source, push to target (unsigned
89 // history lands because the policy is deferred).
90 mirror := t.TempDir()
91 mustGit(t, mirror, env, "clone", "-q", "--mirror", src.sshURL("alice/tool"), "m")
92 mdir := filepath.Join(mirror, "m")
93 mustGit(t, mdir, env, "push", "-q", dst.sshURL("alice/tool"), "+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*")
94 out, _, _ = dst.ssh(t, key, "", "repo", "log", "alice/tool", "--json")
95 if !strings.Contains(out, head) {
96 t.Fatalf("git data missing on target: %s", out)
97 }
98
99 // Resume: a second replay imports nothing twice.
100 out, _, code = dst.ssh(t, key, bundleOut, "account", "import-bundle", "--source", "old.example")
101 if code != 0 || !strings.Contains(out, "imported 0 repos, 0 issues, 0 MRs, 0 comments (1 already present)") {
102 t.Fatalf("re-import: %s", out)
103 }
104}