|
| 1 | +package lockfile |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "os" |
| 6 | + "path/filepath" |
| 7 | + "strings" |
| 8 | + "testing" |
| 9 | + |
| 10 | + "github.com/github/gh-actions-lock/internal/dep" |
| 11 | + "github.com/stretchr/testify/assert" |
| 12 | + "github.com/stretchr/testify/require" |
| 13 | +) |
| 14 | + |
| 15 | +// noNetwork fails the test if a preview asks for repo IDs. |
| 16 | +type noNetwork struct{ t *testing.T } |
| 17 | + |
| 18 | +func (n noNetwork) RepoIDs(context.Context, string, string, string) (int64, int64, error) { |
| 19 | + n.t.Fatal("a preview must not look up repo IDs") |
| 20 | + return 0, 0, nil |
| 21 | +} |
| 22 | + |
| 23 | +func TestPreview(t *testing.T) { |
| 24 | + a, b := strings.Repeat("a", 40), strings.Repeat("b", 40) |
| 25 | + path := filepath.Join(t.TempDir(), "actions.lock") |
| 26 | + body := "version: v0.0.3\nworkflows:\n .github/workflows/ci.yml:\n - o/root@v1\ndependencies:\n" + |
| 27 | + " o/root@v1:\n ref: v1\n commit: sha1-" + a + "\n owner_id: 1\n repo_id: 2\n uses:\n - o/child@v2\n" + |
| 28 | + " o/child@v2:\n ref: v2\n commit: sha1-" + a + "\n owner_id: 1\n repo_id: 3\n" |
| 29 | + require.NoError(t, os.WriteFile(path, []byte(body), 0o600)) |
| 30 | + store, err := LoadStateAt(path, noNetwork{t}) |
| 31 | + require.NoError(t, err) |
| 32 | + before := store.File() |
| 33 | + beforeUses := append([]string(nil), before.Dependencies["o/root@v1"].Uses...) |
| 34 | + |
| 35 | + preview := store.Preview() |
| 36 | + // o/new has no cached IDs: the preview fills a placeholder offline. |
| 37 | + newDep := dep.Dependency{NWO: "o/new", Ref: "v1", SHA: b} |
| 38 | + rootDep := dep.Dependency{NWO: "o/root", Ref: "v1", SHA: b} |
| 39 | + require.NoError(t, preview.Set(context.Background(), ".github/workflows/ci.yml", |
| 40 | + []dep.Dependency{rootDep, newDep}, nil, map[string]bool{rootDep.Key(): true, newDep.Key(): true})) |
| 41 | + preview.GC() |
| 42 | + |
| 43 | + got, ok := preview.Entry("o/root@v1") |
| 44 | + require.True(t, ok) |
| 45 | + assert.Equal(t, "sha1-"+b, got.Commit) |
| 46 | + assert.Equal(t, []string{"o/child@v2"}, got.Uses, "writing a workflow keeps recorded edges") |
| 47 | + _, ok = preview.Entry("o/child@v2") |
| 48 | + assert.True(t, ok, "a recorded edge keeps its child through GC") |
| 49 | + assert.Equal(t, []string{"o/root@v1"}, preview.UsedBy("o/child@v2")) |
| 50 | + _, ok = preview.Entry("o/new@v1") |
| 51 | + assert.True(t, ok) |
| 52 | + |
| 53 | + after := store.File() |
| 54 | + assert.Equal(t, "sha1-"+a, after.Dependencies["o/root@v1"].Commit, "the store is untouched") |
| 55 | + assert.Equal(t, beforeUses, after.Dependencies["o/root@v1"].Uses) |
| 56 | + assert.NotContains(t, after.Dependencies, "o/new@v1") |
| 57 | + assert.ErrorIs(t, preview.Save(), errPreview) |
| 58 | + raw, err := os.ReadFile(path) |
| 59 | + require.NoError(t, err) |
| 60 | + assert.Equal(t, body, string(raw), "nothing reaches disk") |
| 61 | +} |
| 62 | + |
| 63 | +type fixedIDs struct{} |
| 64 | + |
| 65 | +func (fixedIDs) RepoIDs(context.Context, string, string, string) (int64, int64, error) { |
| 66 | + return 7, 8, nil |
| 67 | +} |
| 68 | + |
| 69 | +// TestPreviewProjectsRealSet: a write to a preview leaves the same keys, |
| 70 | +// refs, commits, edges and workflow lists as the same write to the store; |
| 71 | +// only the owner/repo IDs it couldn't look up differ. The preview's |
| 72 | +// placeholder IDs never reach the store's cache. |
| 73 | +func TestPreviewProjectsRealSet(t *testing.T) { |
| 74 | + a, b := strings.Repeat("a", 40), strings.Repeat("b", 40) |
| 75 | + path := filepath.Join(t.TempDir(), "actions.lock") |
| 76 | + body := "version: v0.0.3\nworkflows:\n .github/workflows/ci.yml:\n - o/root@v1\n .github/workflows/other.yml:\n - o/keep@v1\ndependencies:\n" + |
| 77 | + " o/root@v1:\n ref: v1\n commit: sha1-" + a + "\n owner_id: 1\n repo_id: 2\n uses:\n - o/child@v2\n" + |
| 78 | + " o/child@v2:\n ref: v2\n commit: sha1-" + a + "\n owner_id: 1\n repo_id: 3\n" + |
| 79 | + " o/keep@v1:\n ref: v1\n commit: sha1-" + a + "\n owner_id: 1\n repo_id: 4\n" + |
| 80 | + " o/orphan@v1:\n ref: v1\n commit: sha1-" + a + "\n owner_id: 1\n repo_id: 5\n" |
| 81 | + require.NoError(t, os.WriteFile(path, []byte(body), 0o600)) |
| 82 | + store, err := LoadStateAt(path, fixedIDs{}) |
| 83 | + require.NoError(t, err) |
| 84 | + preview := store.Preview() |
| 85 | + |
| 86 | + deps := []dep.Dependency{{NWO: "o/root", Ref: "v1", SHA: b}, {NWO: "o/new", Ref: "v1", SHA: b}} |
| 87 | + direct := map[string]bool{deps[0].Key(): true, deps[1].Key(): true} |
| 88 | + ctx := context.Background() |
| 89 | + require.NoError(t, preview.Set(ctx, ".github/workflows/ci.yml", deps, nil, direct)) |
| 90 | + preview.GC() |
| 91 | + assert.Zero(t, store.RecordedRepoID("o", "new"), "a preview never fills the store's ID cache") |
| 92 | + |
| 93 | + require.NoError(t, store.Set(ctx, ".github/workflows/ci.yml", deps, nil, direct)) |
| 94 | + store.GC() |
| 95 | + project := func(s *State) (map[string][3]string, map[string][]string) { |
| 96 | + f := s.File() |
| 97 | + out := map[string][3]string{} |
| 98 | + for k, a := range f.Dependencies { |
| 99 | + out[k] = [3]string{a.Ref, a.Commit, strings.Join(a.Uses, ",")} |
| 100 | + } |
| 101 | + return out, f.Workflows |
| 102 | + } |
| 103 | + wantDeps, wantWorkflows := project(store) |
| 104 | + gotDeps, gotWorkflows := project(preview) |
| 105 | + assert.Equal(t, wantDeps, gotDeps) |
| 106 | + assert.Equal(t, wantWorkflows, gotWorkflows) |
| 107 | + assert.NotContains(t, gotDeps, "o/orphan@v1", "GC drops what no workflow reaches, as Save does") |
| 108 | +} |
0 commit comments