|
6 | 6 | "testing" |
7 | 7 | "time" |
8 | 8 |
|
| 9 | + "github.com/google/go-github/v71/github" |
9 | 10 | "github.com/rilldata/rill/admin/testadmin" |
| 11 | + "github.com/rilldata/rill/cli/pkg/gitutil" |
10 | 12 | "github.com/rilldata/rill/cli/testcli" |
11 | 13 | adminv1 "github.com/rilldata/rill/proto/gen/rill/admin/v1" |
12 | 14 | "github.com/rilldata/rill/runtime/drivers" |
@@ -118,6 +120,127 @@ vars: |
118 | 120 | }, 10*time.Second, 100*time.Millisecond, "unexpected model output after env set post restart") |
119 | 121 | } |
120 | 122 |
|
| 123 | +func TestPrimaryBranchChange(t *testing.T) { |
| 124 | + testmode.Expensive(t) |
| 125 | + |
| 126 | + adm := testadmin.NewWithOptionalRuntime(t, true) |
| 127 | + _, c := adm.NewUser(t) |
| 128 | + u1 := testcli.New(t, adm, c.Token) |
| 129 | + |
| 130 | + result := u1.Run(t, "org", "create", "branch-change-test") |
| 131 | + require.Equal(t, 0, result.ExitCode) |
| 132 | + |
| 133 | + // deploy the project on main branch |
| 134 | + tempDir := t.TempDir() |
| 135 | + putFiles(t, tempDir, map[string]string{"rill.yaml": `compiler: rillv1 |
| 136 | +display_name: Branch Change Test |
| 137 | +olap_connector: duckdb`, |
| 138 | + }) |
| 139 | + putFiles(t, tempDir, map[string]string{"models/model.sql": "SELECT 'main' AS branch"}) |
| 140 | + result = u1.Run(t, "project", "deploy", "--interactive=false", "--org=branch-change-test", "--project=branch-test", "--path="+tempDir) |
| 141 | + require.Equal(t, 0, result.ExitCode, result.Output) |
| 142 | + |
| 143 | + // manually trigger deployment |
| 144 | + depl := adm.TriggerDeployment(t, "branch-change-test", "branch-test") |
| 145 | + |
| 146 | + // check model output from main branch |
| 147 | + checkModelOutput := func() (string, error) { |
| 148 | + olap, release, err := adm.Runtime.OLAP(t.Context(), depl.RuntimeInstanceID, "duckdb") |
| 149 | + if err != nil { |
| 150 | + return "", err |
| 151 | + } |
| 152 | + defer release() |
| 153 | + |
| 154 | + rows, err := olap.Query(t.Context(), &drivers.Statement{Query: "SELECT branch FROM model"}) |
| 155 | + if err != nil { |
| 156 | + return "", err |
| 157 | + } |
| 158 | + defer rows.Close() |
| 159 | + |
| 160 | + var res string |
| 161 | + for rows.Next() { |
| 162 | + if err := rows.Scan(&res); err != nil { |
| 163 | + return "", err |
| 164 | + } |
| 165 | + } |
| 166 | + if err := rows.Err(); err != nil { |
| 167 | + return "", err |
| 168 | + } |
| 169 | + return res, nil |
| 170 | + } |
| 171 | + require.Eventually(t, func() bool { |
| 172 | + branch, _ := checkModelOutput() |
| 173 | + return branch == "main" |
| 174 | + }, 10*time.Second, 100*time.Millisecond, "expected model output to be 'main'") |
| 175 | + |
| 176 | + // get project to find git remote |
| 177 | + proj, err := c.GetProject(t.Context(), &adminv1.GetProjectRequest{ |
| 178 | + Org: "branch-change-test", |
| 179 | + Project: "branch-test", |
| 180 | + }) |
| 181 | + require.NoError(t, err) |
| 182 | + |
| 183 | + // create a new branch in github with updated model |
| 184 | + installationID, err := adm.Admin.Github.ManagedOrgInstallationID() |
| 185 | + require.NoError(t, err) |
| 186 | + ghClient := adm.Admin.Github.InstallationClient(installationID, nil) |
| 187 | + |
| 188 | + owner, repo, ok := gitutil.SplitGithubRemote(proj.Project.GitRemote) |
| 189 | + require.True(t, ok, "invalid github remote: %s", proj.Project.GitRemote) |
| 190 | + |
| 191 | + // get the current main branch ref |
| 192 | + mainRef, _, err := ghClient.Git.GetRef(t.Context(), owner, repo, "refs/heads/main") |
| 193 | + require.NoError(t, err) |
| 194 | + |
| 195 | + // create new branch "feature" from main |
| 196 | + newBranchRef := "refs/heads/feature" |
| 197 | + _, _, err = ghClient.Git.CreateRef(t.Context(), owner, repo, &github.Reference{ |
| 198 | + Ref: &newBranchRef, |
| 199 | + Object: &github.GitObject{SHA: mainRef.Object.SHA}, |
| 200 | + }) |
| 201 | + require.NoError(t, err) |
| 202 | + |
| 203 | + // update model.sql in the feature branch |
| 204 | + fileContent := "SELECT 'feature' AS branch" |
| 205 | + filePath := "models/model.sql" |
| 206 | + |
| 207 | + // get current file to get its SHA |
| 208 | + fileInfo, _, _, err := ghClient.Repositories.GetContents(t.Context(), owner, repo, filePath, &github.RepositoryContentGetOptions{ |
| 209 | + Ref: "feature", |
| 210 | + }) |
| 211 | + require.NoError(t, err) |
| 212 | + |
| 213 | + // update file in feature branch |
| 214 | + _, _, err = ghClient.Repositories.UpdateFile(t.Context(), owner, repo, filePath, &github.RepositoryContentFileOptions{ |
| 215 | + Message: github.Ptr("Update model for feature branch"), |
| 216 | + Content: []byte(fileContent), |
| 217 | + SHA: fileInfo.SHA, |
| 218 | + Branch: github.Ptr("feature"), |
| 219 | + }) |
| 220 | + require.NoError(t, err) |
| 221 | + |
| 222 | + // change primary branch using project edit |
| 223 | + result = u1.Run(t, "project", "edit", "--primary-branch=feature", "--project=branch-test", "--org=branch-change-test") |
| 224 | + require.Equal(t, 0, result.ExitCode, result.Output) |
| 225 | + |
| 226 | + // verify project primary branch is updated |
| 227 | + proj, err = c.GetProject(t.Context(), &adminv1.GetProjectRequest{ |
| 228 | + Org: "branch-change-test", |
| 229 | + Project: "branch-test", |
| 230 | + }) |
| 231 | + require.NoError(t, err) |
| 232 | + require.Equal(t, "feature", proj.Project.PrimaryBranch) |
| 233 | + |
| 234 | + // manually trigger deployment to pick up new branch |
| 235 | + depl = adm.TriggerDeployment(t, "branch-change-test", "branch-test") |
| 236 | + |
| 237 | + // verify model is updated with changes from feature branch |
| 238 | + require.Eventually(t, func() bool { |
| 239 | + branch, _ := checkModelOutput() |
| 240 | + return branch == "feature" |
| 241 | + }, 10*time.Second, 100*time.Millisecond, "expected model output to be 'feature' after branch change") |
| 242 | +} |
| 243 | + |
121 | 244 | func putFiles(t *testing.T, baseDir string, files map[string]string) { |
122 | 245 | for path, content := range files { |
123 | 246 | path = filepath.Join(baseDir, path) |
|
0 commit comments