Skip to content

Commit 4c772d0

Browse files
committed
fixing error with delete issue
1 parent 82c6efc commit 4c772d0

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

src/clients/apim-client.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,9 @@ export class ApimClient implements IApimClient {
388388

389389
// Poll for long-running operations
390390
if (response.status === 202) {
391-
await this.pollProvisioningState(context, descriptor);
391+
await this.pollProvisioningState(context, descriptor, {
392+
treatMissingAsSuccess: true,
393+
});
392394
}
393395

394396
return true;
@@ -692,14 +694,24 @@ export class ApimClient implements IApimClient {
692694

693695
private async pollProvisioningState(
694696
context: ApimServiceContext,
695-
descriptor: ResourceDescriptor
697+
descriptor: ResourceDescriptor,
698+
options: { treatMissingAsSuccess?: boolean } = {}
696699
): Promise<Record<string, unknown>> {
700+
const { treatMissingAsSuccess = false } = options;
701+
697702
for (let attempt = 0; attempt < ApimClient.MAX_POLLING_ATTEMPTS; attempt++) {
698703
await this.delay(ApimClient.POLL_INTERVAL_MS);
699704

700705
const resource = await this.getResource(context, descriptor);
701706

702707
if (!resource) {
708+
if (treatMissingAsSuccess) {
709+
logger.debug(
710+
`Resource no longer present; operation completed: ${buildResourceLabel(descriptor)}`
711+
);
712+
return {};
713+
}
714+
703715
// APIM can transiently return 404 while asynchronously provisioning a
704716
// resource (e.g. a Key Vault-backed named value). Treat a missing resource
705717
// as "not yet visible" and continue polling rather than aborting — the

tests/unit/clients/apim-client.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,61 @@ describe('ApimClient.putResource provisioning polling', () => {
630630
});
631631
});
632632

633+
describe('ApimClient.deleteResource provisioning polling', () => {
634+
let client: ApimClient;
635+
let fetchSpy: ReturnType<typeof vi.fn>;
636+
637+
beforeEach(() => {
638+
client = new ApimClient();
639+
fetchSpy = vi.fn();
640+
vi.stubGlobal('fetch', fetchSpy);
641+
642+
/* eslint-disable @typescript-eslint/no-explicit-any */
643+
vi.spyOn(client as any, 'getToken').mockResolvedValue('fake-token');
644+
vi.spyOn(client as any, 'delay').mockResolvedValue(undefined);
645+
/* eslint-enable @typescript-eslint/no-explicit-any */
646+
});
647+
648+
afterEach(() => {
649+
vi.restoreAllMocks();
650+
vi.unstubAllGlobals();
651+
});
652+
653+
const descriptor = { type: ResourceType.Api, nameParts: ['delay'] };
654+
655+
it('should treat 404 during delete polling as successful completion', async () => {
656+
fetchSpy
657+
// Initial DELETE accepted asynchronously
658+
.mockResolvedValueOnce(new Response('', { status: 202 }))
659+
// Poll GET returns 404 because delete completed
660+
.mockResolvedValueOnce(new Response('', { status: 404 }));
661+
662+
const deleted = await client.deleteResource(testContext, descriptor);
663+
664+
expect(deleted).toBe(true);
665+
expect(fetchSpy).toHaveBeenCalledTimes(2);
666+
});
667+
668+
it('should continue polling delete until resource disappears', async () => {
669+
fetchSpy
670+
.mockResolvedValueOnce(new Response('', { status: 202 }))
671+
// First poll: resource still exists and is deleting
672+
.mockResolvedValueOnce(
673+
makeResponse(200, {
674+
name: 'delay',
675+
properties: { provisioningState: 'Deleting' },
676+
})
677+
)
678+
// Second poll: gone
679+
.mockResolvedValueOnce(new Response('', { status: 404 }));
680+
681+
const deleted = await client.deleteResource(testContext, descriptor);
682+
683+
expect(deleted).toBe(true);
684+
expect(fetchSpy).toHaveBeenCalledTimes(3);
685+
});
686+
});
687+
633688
describe('ApimClient HTTP 429 rate limiting', () => {
634689
let client: ApimClient;
635690
let fetchSpy: ReturnType<typeof vi.fn>;

0 commit comments

Comments
 (0)