github.com/tilt-dev/tilt@v0.36.0/internal/controllers/core/extensionrepo/reconciler_test.go (about) 1 package extensionrepo 2 3 import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "github.com/spf13/afero" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/types" 16 17 "github.com/tilt-dev/tilt/internal/controllers/fake" 18 "github.com/tilt-dev/tilt/internal/xdg" 19 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 20 ) 21 22 func TestInvalidRepo(t *testing.T) { 23 f := newFixture(t) 24 key := types.NamespacedName{Name: "default"} 25 repo := v1alpha1.ExtensionRepo{ 26 ObjectMeta: metav1.ObjectMeta{ 27 Name: key.Name, 28 }, 29 Spec: v1alpha1.ExtensionRepoSpec{ 30 URL: "x", 31 }, 32 } 33 f.Create(&repo) 34 f.MustGet(key, &repo) 35 assert.Equal(t, "invalid: URL must start with 'https://': x", repo.Status.Error) 36 assert.Equal(t, "extensionrepo default: invalid: URL must start with 'https://': x\n", f.Stdout()) 37 } 38 39 func TestUnknown(t *testing.T) { 40 f := newFixture(t) 41 f.dlr.downloadError = fmt.Errorf("X") 42 key := types.NamespacedName{Name: "default"} 43 repo := v1alpha1.ExtensionRepo{ 44 ObjectMeta: metav1.ObjectMeta{ 45 Name: key.Name, 46 }, 47 Spec: v1alpha1.ExtensionRepoSpec{ 48 URL: "https://github.com/tilt-dev/unknown-repo", 49 }, 50 } 51 f.Create(&repo) 52 f.MustGet(key, &repo) 53 require.Contains(t, repo.Status.Error, "download error: waiting 5s before retrying") 54 } 55 56 func TestDefaultWeb(t *testing.T) { 57 f := newFixture(t) 58 key := types.NamespacedName{Name: "default"} 59 repo := v1alpha1.ExtensionRepo{ 60 ObjectMeta: metav1.ObjectMeta{ 61 Name: key.Name, 62 }, 63 Spec: v1alpha1.ExtensionRepoSpec{ 64 URL: "https://github.com/tilt-dev/tilt-extensions", 65 }, 66 } 67 f.Create(&repo) 68 f.MustGet(key, &repo) 69 require.Equal(t, repo.Status.Error, "") 70 require.True(t, strings.HasSuffix(repo.Status.Path, "tilt-extensions")) 71 require.Equal(t, repo.Status.CheckoutRef, "fake-head") 72 73 info, err := os.Stat(repo.Status.Path) 74 require.NoError(t, err) 75 require.True(t, info.IsDir()) 76 assert.Equal(t, 1, f.dlr.downloadCount) 77 assert.Equal(t, "", f.dlr.lastRefSync) 78 79 f.MustReconcile(key) 80 assert.Equal(t, 1, f.dlr.downloadCount) 81 82 f.Delete(&repo) 83 84 _, err = os.Stat(repo.Status.Path) 85 require.True(t, os.IsNotExist(err)) 86 } 87 88 func TestDefaultFile(t *testing.T) { 89 f := newFixture(t) 90 91 path := filepath.Join(f.base.Dir, "my-repo") 92 _ = os.MkdirAll(path, os.FileMode(0755)) 93 94 key := types.NamespacedName{Name: "default"} 95 repo := v1alpha1.ExtensionRepo{ 96 ObjectMeta: metav1.ObjectMeta{ 97 Name: key.Name, 98 }, 99 Spec: v1alpha1.ExtensionRepoSpec{ 100 URL: fmt.Sprintf("file://%s", path), 101 }, 102 } 103 f.Create(&repo) 104 f.MustGet(key, &repo) 105 require.Equal(t, repo.Status.Error, "") 106 require.Equal(t, repo.Status.Path, path) 107 } 108 109 func TestRepoSync(t *testing.T) { 110 f := newFixture(t) 111 112 key := types.NamespacedName{Name: "default"} 113 repo := v1alpha1.ExtensionRepo{ 114 ObjectMeta: metav1.ObjectMeta{ 115 Name: key.Name, 116 }, 117 Spec: v1alpha1.ExtensionRepoSpec{ 118 URL: "https://github.com/tilt-dev/tilt-extensions", 119 Ref: "other-ref", 120 }, 121 } 122 f.Create(&repo) 123 f.MustGet(key, &repo) 124 require.Equal(t, repo.Status.Error, "") 125 assert.Equal(t, 1, f.dlr.downloadCount) 126 assert.Equal(t, "other-ref", f.dlr.lastRefSync) 127 f.assertSteadyState(&repo) 128 } 129 130 func TestRepoSyncExisting(t *testing.T) { 131 f := newFixture(t) 132 133 key := types.NamespacedName{Name: "default"} 134 repo := v1alpha1.ExtensionRepo{ 135 ObjectMeta: metav1.ObjectMeta{ 136 Name: key.Name, 137 }, 138 Spec: v1alpha1.ExtensionRepoSpec{ 139 URL: "https://github.com/tilt-dev/tilt-extensions", 140 Ref: "other-ref", 141 }, 142 } 143 144 f.dlr.Download("github.com/tilt-dev/tilt-extensions") 145 f.dlr.RefSync("github.com/tilt-dev/tilt-extensions", "other-ref") 146 147 f.Create(&repo) 148 f.MustGet(key, &repo) 149 require.Equal(t, repo.Status.Error, "") 150 assert.Equal(t, 1, f.dlr.downloadCount) 151 assert.Equal(t, "other-ref", f.dlr.lastRefSync) 152 f.assertSteadyState(&repo) 153 } 154 155 func TestRepoAlwaysSyncHead(t *testing.T) { 156 f := newFixture(t) 157 158 key := types.NamespacedName{Name: "default"} 159 repo := v1alpha1.ExtensionRepo{ 160 ObjectMeta: metav1.ObjectMeta{ 161 Name: key.Name, 162 }, 163 Spec: v1alpha1.ExtensionRepoSpec{ 164 URL: "https://github.com/tilt-dev/tilt-extensions", 165 Ref: "HEAD", 166 }, 167 } 168 169 f.dlr.Download("github.com/tilt-dev/tilt-extensions") 170 f.dlr.RefSync("github.com/tilt-dev/tilt-extensions", "HEAD") 171 172 f.Create(&repo) 173 f.MustGet(key, &repo) 174 require.Equal(t, repo.Status.Error, "") 175 assert.Equal(t, 2, f.dlr.downloadCount) 176 assert.Equal(t, "HEAD", f.dlr.lastRefSync) 177 f.assertSteadyState(&repo) 178 } 179 180 func TestStale(t *testing.T) { 181 f := newFixture(t) 182 183 f.dlr.Download("github.com/tilt-dev/stale-repo") 184 f.dlr.downloadError = fmt.Errorf("fake error") 185 186 key := types.NamespacedName{Name: "default"} 187 repo := v1alpha1.ExtensionRepo{ 188 ObjectMeta: metav1.ObjectMeta{ 189 Name: key.Name, 190 }, 191 Spec: v1alpha1.ExtensionRepoSpec{ 192 URL: "https://github.com/tilt-dev/stale-repo", 193 }, 194 } 195 f.Create(&repo) 196 f.MustGet(key, &repo) 197 require.Contains(t, repo.Status.Error, "") 198 require.Contains(t, repo.Status.StaleReason, "fake error") 199 } 200 201 type fixture struct { 202 *fake.ControllerFixture 203 r *Reconciler 204 dlr *fakeDownloader 205 base *xdg.FakeBase 206 } 207 208 func newFixture(t *testing.T) *fixture { 209 cfb := fake.NewControllerFixtureBuilder(t) 210 tmpDir := t.TempDir() 211 fs := afero.NewOsFs() 212 base := xdg.NewFakeBase(tmpDir, fs) 213 r, err := NewReconciler(cfb.Client, cfb.Store, base) 214 require.NoError(t, err) 215 216 dlr := &fakeDownloader{base: base, headRef: "fake-head"} 217 r.dlr = dlr 218 219 return &fixture{ 220 ControllerFixture: cfb.Build(r), 221 r: r, 222 dlr: dlr, 223 base: base, 224 } 225 } 226 227 func (f *fixture) assertSteadyState(er *v1alpha1.ExtensionRepo) { 228 f.T().Helper() 229 f.MustReconcile(types.NamespacedName{Name: er.Name}) 230 var er2 v1alpha1.ExtensionRepo 231 f.MustGet(types.NamespacedName{Name: er.Name}, &er2) 232 assert.Equal(f.T(), er.ResourceVersion, er2.ResourceVersion) 233 } 234 235 type fakeDownloader struct { 236 base xdg.Base 237 238 downloadError error 239 downloadCount int 240 lastRefSync string 241 headRef string 242 } 243 244 func (d *fakeDownloader) DestinationPath(pkg string) string { 245 result, _ := d.base.DataFile(pkg) 246 return result 247 } 248 249 func (d *fakeDownloader) Download(pkg string) (string, error) { 250 251 d.downloadCount += 1 252 if d.downloadError != nil { 253 return "", fmt.Errorf("download error %d: %v", d.downloadCount, d.downloadError) 254 } 255 256 path, err := d.base.DataFile(filepath.Join(pkg, "Tiltfile")) 257 if err != nil { 258 return "", err 259 } 260 261 _, err = os.Stat(path) 262 exists := err == nil 263 if exists && d.lastRefSync != "" && d.lastRefSync != "HEAD" { 264 // If the current disk state is checked out to a ref, then 265 // we expect Download() to fail. 266 // https://github.com/tilt-dev/tilt/issues/5508 267 return "", fmt.Errorf("You are not currently on a branch.") 268 } 269 270 err = os.WriteFile(path, 271 []byte(fmt.Sprintf("Download count %d", d.downloadCount)), fs.FileMode(0777)) 272 return "", err 273 } 274 275 func (d *fakeDownloader) HeadRef(pkg string) (string, error) { 276 return d.headRef, nil 277 } 278 279 func (d *fakeDownloader) RefSync(pkg string, ref string) error { 280 d.lastRefSync = ref 281 return nil 282 }