Skip to content

Commit 9bcfb1b

Browse files
CopilotYunchuWang
andcommitted
Add integration tests for Suspend/Resume functionality
- Added 5 new integration tests for suspend/resume operations - Tests cover basic suspend/resume, with/without reasons, and edge cases - All tests passing successfully Co-authored-by: YunchuWang <[email protected]>
1 parent f70e8e5 commit 9bcfb1b

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed

test/Grpc.IntegrationTests/GrpcDurableTaskClientIntegrationTests.cs

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,203 @@ await restartAction.Should().ThrowAsync<ArgumentException>()
288288
.WithMessage("*An orchestration with the instanceId non-existent-instance-id was not found*");
289289
}
290290

291+
[Fact]
292+
public async Task SuspendAndResumeInstance_EndToEnd()
293+
{
294+
// Arrange
295+
await using HostTestLifetime server = await this.StartLongRunningAsync();
296+
297+
string instanceId = await server.Client.ScheduleNewOrchestrationInstanceAsync(
298+
OrchestrationName, input: false);
299+
300+
// Wait for the orchestration to start
301+
await server.Client.WaitForInstanceStartAsync(instanceId, default);
302+
303+
// Act - Suspend the orchestration
304+
await server.Client.SuspendInstanceAsync(instanceId, "Test suspension", default);
305+
306+
// Poll for suspended status (up to 5 seconds)
307+
OrchestrationMetadata? suspendedMetadata = null;
308+
DateTime deadline = DateTime.UtcNow.AddSeconds(5);
309+
while (DateTime.UtcNow < deadline)
310+
{
311+
suspendedMetadata = await server.Client.GetInstanceAsync(instanceId, false);
312+
if (suspendedMetadata?.RuntimeStatus == OrchestrationRuntimeStatus.Suspended)
313+
{
314+
break;
315+
}
316+
317+
await Task.Delay(TimeSpan.FromMilliseconds(100));
318+
}
319+
320+
// Assert - Verify orchestration is suspended
321+
suspendedMetadata.Should().NotBeNull();
322+
suspendedMetadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Suspended);
323+
suspendedMetadata.InstanceId.Should().Be(instanceId);
324+
325+
// Act - Resume the orchestration
326+
await server.Client.ResumeInstanceAsync(instanceId, "Test resumption", default);
327+
328+
// Poll for running status (up to 5 seconds)
329+
OrchestrationMetadata? resumedMetadata = null;
330+
deadline = DateTime.UtcNow.AddSeconds(5);
331+
while (DateTime.UtcNow < deadline)
332+
{
333+
resumedMetadata = await server.Client.GetInstanceAsync(instanceId, false);
334+
if (resumedMetadata?.RuntimeStatus == OrchestrationRuntimeStatus.Running)
335+
{
336+
break;
337+
}
338+
339+
await Task.Delay(TimeSpan.FromMilliseconds(100));
340+
}
341+
342+
// Assert - Verify orchestration is running again
343+
resumedMetadata.Should().NotBeNull();
344+
resumedMetadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Running);
345+
346+
// Complete the orchestration
347+
await server.Client.RaiseEventAsync(instanceId, "event", default);
348+
await server.Client.WaitForInstanceCompletionAsync(instanceId, default);
349+
350+
// Verify the orchestration completed successfully
351+
OrchestrationMetadata? completedMetadata = await server.Client.GetInstanceAsync(instanceId, false);
352+
completedMetadata.Should().NotBeNull();
353+
completedMetadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);
354+
}
355+
356+
[Fact]
357+
public async Task SuspendInstance_WithoutReason_Succeeds()
358+
{
359+
// Arrange
360+
await using HostTestLifetime server = await this.StartLongRunningAsync();
361+
362+
string instanceId = await server.Client.ScheduleNewOrchestrationInstanceAsync(
363+
OrchestrationName, input: false);
364+
365+
await server.Client.WaitForInstanceStartAsync(instanceId, default);
366+
367+
// Act - Suspend without a reason
368+
await server.Client.SuspendInstanceAsync(instanceId, cancellation: default);
369+
370+
// Poll for suspended status (up to 5 seconds)
371+
OrchestrationMetadata? suspendedMetadata = null;
372+
DateTime deadline = DateTime.UtcNow.AddSeconds(5);
373+
while (DateTime.UtcNow < deadline)
374+
{
375+
suspendedMetadata = await server.Client.GetInstanceAsync(instanceId, false);
376+
if (suspendedMetadata?.RuntimeStatus == OrchestrationRuntimeStatus.Suspended)
377+
{
378+
break;
379+
}
380+
381+
await Task.Delay(TimeSpan.FromMilliseconds(100));
382+
}
383+
384+
// Assert
385+
suspendedMetadata.Should().NotBeNull();
386+
suspendedMetadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Suspended);
387+
}
388+
389+
[Fact]
390+
public async Task ResumeInstance_WithoutReason_Succeeds()
391+
{
392+
// Arrange
393+
await using HostTestLifetime server = await this.StartLongRunningAsync();
394+
395+
string instanceId = await server.Client.ScheduleNewOrchestrationInstanceAsync(
396+
OrchestrationName, input: false);
397+
398+
await server.Client.WaitForInstanceStartAsync(instanceId, default);
399+
await server.Client.SuspendInstanceAsync(instanceId, "Test suspension", default);
400+
401+
// Wait for suspension
402+
DateTime deadline = DateTime.UtcNow.AddSeconds(5);
403+
while (DateTime.UtcNow < deadline)
404+
{
405+
OrchestrationMetadata? metadata = await server.Client.GetInstanceAsync(instanceId, false);
406+
if (metadata?.RuntimeStatus == OrchestrationRuntimeStatus.Suspended)
407+
{
408+
break;
409+
}
410+
411+
await Task.Delay(TimeSpan.FromMilliseconds(100));
412+
}
413+
414+
// Act - Resume without a reason
415+
await server.Client.ResumeInstanceAsync(instanceId, cancellation: default);
416+
417+
// Poll for running status (up to 5 seconds)
418+
OrchestrationMetadata? resumedMetadata = null;
419+
deadline = DateTime.UtcNow.AddSeconds(5);
420+
while (DateTime.UtcNow < deadline)
421+
{
422+
resumedMetadata = await server.Client.GetInstanceAsync(instanceId, false);
423+
if (resumedMetadata?.RuntimeStatus == OrchestrationRuntimeStatus.Running)
424+
{
425+
break;
426+
}
427+
428+
await Task.Delay(TimeSpan.FromMilliseconds(100));
429+
}
430+
431+
// Assert
432+
resumedMetadata.Should().NotBeNull();
433+
resumedMetadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Running);
434+
}
435+
436+
[Fact]
437+
public async Task SuspendInstance_AlreadyCompleted_NoError()
438+
{
439+
// Arrange
440+
await using HostTestLifetime server = await this.StartAsync();
441+
442+
string instanceId = await server.Client.ScheduleNewOrchestrationInstanceAsync(
443+
OrchestrationName, input: false);
444+
445+
await server.Client.WaitForInstanceStartAsync(instanceId, default);
446+
await server.Client.RaiseEventAsync(instanceId, "event", default);
447+
await server.Client.WaitForInstanceCompletionAsync(instanceId, default);
448+
449+
// Verify it's completed
450+
OrchestrationMetadata? completedMetadata = await server.Client.GetInstanceAsync(instanceId, false);
451+
completedMetadata.Should().NotBeNull();
452+
completedMetadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);
453+
454+
// Act - Try to suspend a completed orchestration (should not throw)
455+
await server.Client.SuspendInstanceAsync(instanceId, "Test suspension", default);
456+
457+
// Assert - Status should remain completed
458+
OrchestrationMetadata? metadata = await server.Client.GetInstanceAsync(instanceId, false);
459+
metadata.Should().NotBeNull();
460+
metadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);
461+
}
462+
463+
[Fact]
464+
public async Task ResumeInstance_NotSuspended_NoError()
465+
{
466+
// Arrange
467+
await using HostTestLifetime server = await this.StartLongRunningAsync();
468+
469+
string instanceId = await server.Client.ScheduleNewOrchestrationInstanceAsync(
470+
OrchestrationName, input: false);
471+
472+
await server.Client.WaitForInstanceStartAsync(instanceId, default);
473+
474+
// Verify it's running
475+
OrchestrationMetadata? runningMetadata = await server.Client.GetInstanceAsync(instanceId, false);
476+
runningMetadata.Should().NotBeNull();
477+
runningMetadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Running);
478+
479+
// Act - Try to resume an already running orchestration (should not throw)
480+
await server.Client.ResumeInstanceAsync(instanceId, "Test resumption", default);
481+
482+
// Assert - Status should remain running
483+
OrchestrationMetadata? metadata = await server.Client.GetInstanceAsync(instanceId, false);
484+
metadata.Should().NotBeNull();
485+
metadata!.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Running);
486+
}
487+
291488
Task<HostTestLifetime> StartAsync()
292489
{
293490
static async Task<string> Orchestration(TaskOrchestrationContext context, bool shouldThrow)
@@ -308,6 +505,30 @@ static async Task<string> Orchestration(TaskOrchestrationContext context, bool s
308505
});
309506
}
310507

508+
Task<HostTestLifetime> StartLongRunningAsync()
509+
{
510+
static async Task<string> LongRunningOrchestration(TaskOrchestrationContext context, bool shouldThrow)
511+
{
512+
context.SetCustomStatus("waiting");
513+
// Wait for external event or a long timer (5 minutes) to allow suspend/resume operations
514+
Task<string> eventTask = context.WaitForExternalEvent<string>("event");
515+
Task timerTask = context.CreateTimer(TimeSpan.FromMinutes(5), CancellationToken.None);
516+
await Task.WhenAny(eventTask, timerTask);
517+
518+
if (shouldThrow)
519+
{
520+
throw new InvalidOperationException("Orchestration failed");
521+
}
522+
523+
return $"{shouldThrow} -> output";
524+
}
525+
526+
return this.StartWorkerAsync(b =>
527+
{
528+
b.AddTasks(tasks => tasks.AddOrchestratorFunc<bool, string>(OrchestrationName, LongRunningOrchestration));
529+
});
530+
}
531+
311532
class DateTimeToleranceComparer : IEqualityComparer<DateTimeOffset>
312533
{
313534
public bool Equals(DateTimeOffset x, DateTimeOffset y) => (x - y).Duration() < TimeSpan.FromMilliseconds(100);

0 commit comments

Comments
 (0)