test(git-sync): cover fork-of-dev-workspace branch naming and routing (#10231)

* test(git-sync): cover fork-of-dev-workspace branch naming and routing

A throwaway fork of a dev workspace pushes to `wm-fork/<tracked>/<id>` (the
tracked branch, not the dev's label), and the root's `sync_forks` poller
enumerates `wm-fork/<tracked>/*` and routes commits on that branch into the
nested fork through the root. This was twice assumed to instead live on
`wm-fork/<dev-label>/<id>` and therefore never be collected/reconciled; these
tests pin the real behavior.

- CLI unit: `computeGitSyncDeployBranch` for a fork whose parent is a dev
  workspace resolves to `wm-fork/main/<id>`, explicitly not `wm-fork/dev/<id>`.
- git-sync E2E: fork a dev workspace, assert the created branch is
  `wm-fork/main/<id>` (not `wm-fork/dev/*`), then assert a commit on it deploys
  into the fork via the root's sync_forks poller while the root is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(git-sync): reconcile single-dev-per-root in fork-of-dev e2e

The root workspace allows only one dev workspace, and a sibling test leaves one
attached, so attach_dev_workspace failed with "already has a dev workspace".
Detach any pre-existing dev before attaching, and detach ours via addCleanup so
the test doesn't leak its own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to aaa6cb89b05b76139252c64f057e53b94d12ac60

This commit updates the EE repository reference after PR #680 was merged in windmill-ee-private.

Previous ee-repo-ref: 4c08634af953db5c1125b1fb03f5af211fe21db3

New ee-repo-ref: aaa6cb89b05b76139252c64f057e53b94d12ac60

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
hugocasa
2026-07-21 19:04:39 +02:00
committed by GitHub
parent 7ac27c1ef2
commit dae7c49f21
2 changed files with 131 additions and 0 deletions
+20
View File
@@ -199,6 +199,26 @@ describe("computeGitSyncDeployBranch", () => {
})
).toBe("wm-fork/main/mydev");
});
// A throwaway fork OF a dev workspace is named after the tracked (cloned)
// branch, NOT the parent dev's label: the child is not itself a dev
// workspace, so it carries no devWorkspaceLabel. The dev label reaches this
// deploy only as the checkout base + PR target (handled in sync.ts), never as
// the branch name — otherwise the branch would be `wm-fork/<label>/<id>` and
// the poller's `wm-fork/<tracked>/*` enumeration would miss it.
test("throwaway fork of a dev workspace -> wm-fork/<tracked>/<id>, not the dev label", () => {
const branch = computeGitSyncDeployBranch({
workspaceId: "wm-fork-child",
parentWorkspaceId: "prodstaging", // the dev workspace (prefix-less id)
devWorkspaceLabel: undefined, // child is not a dev workspace
clonedBranchName: "main",
groupByFolder: false,
useIndividualBranch: false,
items: [{ path_type: "script", path: "f/foo/bar" }],
});
expect(branch).toBe("wm-fork/main/child");
expect(branch).not.toBe("wm-fork/dev/child");
});
});
describe("forkBranchName", () => {
+111
View File
@@ -943,6 +943,117 @@ class TestGitSyncAutoPull(GitSyncTestBase):
"Parent workspace received a commit from a fork branch",
)
def test_fork_of_dev_workspace_branch_deploys_into_fork(self):
"""A throwaway fork OF a dev workspace pushes to wm-fork/<tracked>/<id>
(the tracked branch, NOT the dev's label), and the root's sync_forks
poller enumerates wm-fork/<tracked>/* and routes a commit on that branch
into the nested fork — through the root, since only the root holds
auto-pull config. Regression for the assumption that such a fork lives on
wm-fork/<dev-label>/<id> and is therefore never collected/reconciled."""
repo_name, _ = self._create_test_repo()
resource_path = self._setup_git_sync_resource(repo_name)
self._configure_single_repo_sync(resource_path, include_type=["script"])
script_path = self._deploy_seed_script("forkofdev")
script_file = self._repo_script_file(repo_name, script_path)
# Seed before the fork branch is created so the branch inherits it.
self._seed_wmill_yaml(repo_name)
self._configure_auto_pull(resource_path, sync_forks=True)
# Attach an existing workspace as a dev workspace of the root (label
# "dev"). It carries the same inherited sync repo so a fork beneath it
# resolves against it.
dev_id = f"it-dev-{uuid.uuid4().hex[:8]}"
self._fork_workspaces_to_cleanup.append(dev_id)
dev_client = WindmillClient(workspace=dev_id)
dev_client.create_resource(
path=resource_path,
resource_type="git_repository",
value={
"url": self._gitea.get_docker_clone_url(repo_name),
"branch": "main",
"is_github_app": False,
},
update_if_exists=True,
)
dev_client.configure_git_sync({
"repositories": [{
"git_repo_resource_path": f"$res:{resource_path}",
"use_individual_branch": False,
"group_by_folder": False,
"settings": {"include_type": ["script"], "include_path": ["**"]},
}],
})
root = self._client._workspace
# Only one dev workspace per root, so clear any left attached by a prior
# test before attaching ours, and detach ours afterward so we don't leak.
existing = self._client._client.get(
f"/api/w/{root}/workspaces/get_dev_workspace"
)
if existing.status_code == 200 and existing.json():
self._client._client.post(
f"/api/w/{root}/workspaces/detach_dev_workspace",
json={"dev_workspace_id": existing.json()["id"]},
)
self.addCleanup(
lambda: self._client._client.post(
f"/api/w/{root}/workspaces/detach_dev_workspace",
json={"dev_workspace_id": dev_id},
)
)
attach = self._client._client.post(
f"/api/w/{root}/workspaces/attach_dev_workspace",
json={"dev_workspace_id": dev_id, "dev_workspace_label": "dev"},
)
self.assertEqual(
attach.status_code // 100,
2,
f"attach_dev_workspace failed: {attach.content.decode()}",
)
# Fork the dev workspace (branch first, then workspace) — its parent is
# the dev, so this is a fork OF a dev workspace.
fork_id = f"wm-fork-{uuid.uuid4().hex[:8]}"
self._fork_workspaces_to_cleanup.append(fork_id)
job_ids = dev_client.create_workspace_fork_branch(fork_id, f"Fork {fork_id}")
if job_ids:
dev_client.wait_for_jobs_by_ids(job_ids, timeout=90)
time.sleep(3)
dev_client.create_workspace_fork(fork_id, f"Fork {fork_id}")
# The fork branch is named after the tracked branch, not the dev label.
fork_suffix = fork_id[len("wm-fork-"):]
fork_branch = f"wm-fork/main/{fork_suffix}"
branches = self._get_branches(self._clone_repo_all_branches(repo_name))
self.assertTrue(
any(fork_branch in b for b in branches),
f"expected {fork_branch} in the repo after forking the dev, got: {branches}",
)
self.assertFalse(
any(f"wm-fork/dev/{fork_suffix}" in b for b in branches),
f"fork-of-dev must not live on a wm-fork/<dev-label>/* branch: {branches}",
)
self._gitea.create_file(
repo_name, script_file, ts_script("return 'fork only'"),
branch=fork_branch,
)
fork_client = WindmillClient(workspace=fork_id)
self._wait_until(
lambda: "fork only" in fork_client.get_script_content(script_path),
timeout=self.PULL_TIMEOUT,
message=f"fork-of-dev workspace {fork_id} did not receive the fork-branch commit",
)
# The root (the fork's grandparent, holder of the poller) must not see it.
self.assertNotIn(
"fork only",
self._client.get_script_content(script_path),
"Root workspace received a commit from a fork-of-dev branch",
)
def test_settings_normalization_and_redaction(self):
"""Webhook mode on a token repo is persisted as polling; server-owned
webhook fields are never exposed; legacy repos gain no auto_pull key."""