mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-10 17:36:04 +00:00
fix(gitops): map an in-flight refusal during source save to 409
A PUT that changes the source policy while a fetch is in flight threw GitOpsTransitionError out of the upsert transaction. The rollback was correct, but the route mapped the throw to a generic 500, hiding the actionable reason. Suspend and dismiss already translate this error to an OPERATION_IN_FLIGHT GitSourceError; the save path now does the same, and a route test pins the 409 and the full rollback.
This commit is contained in:
@@ -2550,6 +2550,36 @@ describe('git-source policy compatibility', () => {
|
||||
expect(res.body.error).toMatch(/source_policy/i);
|
||||
});
|
||||
|
||||
it('PUT racing an in-flight operation maps the refusal to 409', async () => {
|
||||
const stackName = 'policy-put-in-flight';
|
||||
seedStackDir(stackName);
|
||||
seedGitSource(stackName);
|
||||
seedApp(stackName, 'automatic');
|
||||
// Open a fetch on the application so the policy transition refuses.
|
||||
// The PUT transaction must roll back whole, and the specific refusal
|
||||
// must reach the operator as 409, not as a generic 500.
|
||||
GitOpsTransitions.getInstance().fetchStarted(
|
||||
`policy-app-${stackName}`,
|
||||
{ operationId: 'fetch-policy-put', actor: 'test', trigger: 'poll', at: Date.now() },
|
||||
);
|
||||
const fetchFromGit = stubFetchFromGit();
|
||||
try {
|
||||
const res = await request(app)
|
||||
.put(`/api/stacks/${stackName}/git-source`)
|
||||
.set('Authorization', `Bearer ${adminToken()}`)
|
||||
.send(putBody({ auto_apply_on_webhook: false, source_policy: 'review' }));
|
||||
expect(res.status).toBe(409);
|
||||
expect(res.body.error).toMatch(/in flight/);
|
||||
// The whole save rolled back: the policy did not change under the
|
||||
// refused PUT.
|
||||
const application = GitOpsStore.getInstance().getLiveDirectApplication(stackName);
|
||||
expect(application?.source_policy).toBe('automatic');
|
||||
} finally {
|
||||
fetchFromGit.mockRestore();
|
||||
deleteRows(stackName);
|
||||
}
|
||||
});
|
||||
|
||||
it('read projects auto_apply_on_webhook true only for automatic', async () => {
|
||||
const stackName = 'policy-read-projection';
|
||||
seedStackDir(stackName);
|
||||
|
||||
@@ -911,8 +911,11 @@ export class GitSourceService {
|
||||
// The source row, the pending clear, and the GitOps transition commit
|
||||
// together. Clearing pending without invalidating the candidate would
|
||||
// leave the model offering an apply for files the operator can no
|
||||
// longer produce.
|
||||
db.getDb().transaction(() => {
|
||||
// longer produce. A refused transition (an operation went in flight)
|
||||
// aborts the whole save; it is mapped below so the operator sees the
|
||||
// specific refusal, not a generic 500.
|
||||
try {
|
||||
db.getDb().transaction(() => {
|
||||
db.upsertGitSource({
|
||||
stack_name: input.stackName,
|
||||
repo_url: input.repoUrl,
|
||||
@@ -990,6 +993,12 @@ export class GitSourceService {
|
||||
GitOpsTransitions.getInstance().sourcePolicyChanged(app.id, effectivePolicy, envelope);
|
||||
}
|
||||
})();
|
||||
} catch (error) {
|
||||
if (error instanceof GitOpsTransitionError) {
|
||||
throw new GitSourceError('OPERATION_IN_FLIGHT', `Cannot save the Git source for ${input.stackName}: ${error.message}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (
|
||||
input.authType === 'deploy_key'
|
||||
|
||||
Reference in New Issue
Block a user