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