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