Skip to content

Commit 1dcf80d

Browse files
Add smoke tests for Nexus + Time skipping test server
1 parent b5fd015 commit 1dcf80d

1 file changed

Lines changed: 175 additions & 0 deletions

File tree

tests/Temporalio.Tests/Testing/WorkflowEnvironmentTests.cs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ namespace Temporalio.Tests.Testing;
22

33
using System.Diagnostics;
44
using System.Runtime.InteropServices;
5+
using NexusRpc;
6+
using NexusRpc.Handlers;
57
using Temporalio.Activities;
68
using Temporalio.Api.Enums.V1;
79
using Temporalio.Client;
810
using Temporalio.Common;
911
using Temporalio.Exceptions;
12+
using Temporalio.Nexus;
1013
using Temporalio.Testing;
1114
using Temporalio.Worker;
1215
using Temporalio.Workflows;
@@ -241,6 +244,178 @@ await env.WithAutoTimeSkippingDisabledAsync(async () =>
241244
});
242245
}
243246

247+
[NexusService]
248+
public interface INexusLongRunningService
249+
{
250+
[NexusOperation]
251+
string RunLongOperation(string input);
252+
}
253+
254+
[Workflow]
255+
public class NexusLongRunningWorkflow
256+
{
257+
[WorkflowRun]
258+
public async Task<string> RunAsync(string input)
259+
{
260+
await Workflow.DelayAsync(TimeSpan.FromDays(2));
261+
return $"done: {input}";
262+
}
263+
}
264+
265+
[NexusServiceHandler(typeof(INexusLongRunningService))]
266+
public class NexusLongRunningServiceHandler
267+
{
268+
[NexusOperationHandler]
269+
public IOperationHandler<string, string> RunLongOperation() =>
270+
WorkflowRunOperationHandler.FromHandleFactory(
271+
(WorkflowRunOperationContext context, string input) =>
272+
context.StartWorkflowAsync(
273+
(NexusLongRunningWorkflow wf) => wf.RunAsync(input),
274+
new() { Id = $"nexus-wf-{Guid.NewGuid()}" }));
275+
}
276+
277+
[Workflow]
278+
public class NexusCallerWorkflow
279+
{
280+
private readonly string endpoint;
281+
282+
public NexusCallerWorkflow(string endpoint) => this.endpoint = endpoint;
283+
284+
[WorkflowRun]
285+
public async Task<string> RunAsync(string input)
286+
{
287+
return await Workflow.CreateNexusClient<INexusLongRunningService>(endpoint).
288+
ExecuteNexusOperationAsync(svc => svc.RunLongOperation(input));
289+
}
290+
}
291+
292+
[OnlyIntelFact]
293+
public async Task StartTimeSkippingAsync_NexusLongRunningWorkflow_ProperlySkips()
294+
{
295+
await using var env = await WorkflowEnvironment.StartTimeSkippingAsync();
296+
var taskQueue = $"tq-{Guid.NewGuid()}";
297+
var endpointName = $"nexus-endpoint-{taskQueue}";
298+
await env.CreateNexusEndpointAsync(endpointName, taskQueue);
299+
using var worker = new TemporalWorker(
300+
env.Client,
301+
new TemporalWorkerOptions(taskQueue).
302+
AddNexusService(new NexusLongRunningServiceHandler()).
303+
AddWorkflow<NexusLongRunningWorkflow>().
304+
AddWorkflow(WorkflowDefinition.Create(
305+
typeof(NexusCallerWorkflow),
306+
null,
307+
_args => new NexusCallerWorkflow(endpointName))));
308+
await worker.ExecuteAsync(async () =>
309+
{
310+
// Check that timestamp is around now
311+
AssertMore.DateTimeFromUtcNow(await env.GetCurrentTimeAsync(), TimeSpan.Zero);
312+
313+
// Run the caller workflow which invokes the Nexus operation that starts a
314+
// long-running workflow with a 2-day delay
315+
var watch = Stopwatch.StartNew();
316+
var result = await env.Client.ExecuteWorkflowAsync(
317+
(NexusCallerWorkflow wf) => wf.RunAsync("test-input"),
318+
new(id: $"workflow-{Guid.NewGuid()}", taskQueue: taskQueue));
319+
Assert.Equal("done: test-input", result);
320+
321+
// Verify time was skipped (should complete much faster than 2 days)
322+
Assert.True(watch.Elapsed < TimeSpan.FromSeconds(30));
323+
324+
// Check that the server time advanced ~2 days
325+
AssertMore.DateTimeFromUtcNow(await env.GetCurrentTimeAsync(), TimeSpan.FromDays(2));
326+
});
327+
}
328+
329+
[OnlyIntelFact]
330+
public async Task StartTimeSkippingAsync_NexusLongRunningWorkflow_ManualSkipAndCancel()
331+
{
332+
await using var env = await WorkflowEnvironment.StartTimeSkippingAsync();
333+
var taskQueue = $"tq-{Guid.NewGuid()}";
334+
var endpointName = $"nexus-endpoint-{taskQueue}";
335+
await env.CreateNexusEndpointAsync(endpointName, taskQueue);
336+
using var worker = new TemporalWorker(
337+
env.Client,
338+
new TemporalWorkerOptions(taskQueue).
339+
AddNexusService(new NexusLongRunningServiceHandler()).
340+
AddWorkflow<NexusLongRunningWorkflow>().
341+
AddWorkflow(WorkflowDefinition.Create(
342+
typeof(NexusCallerWorkflow),
343+
null,
344+
_args => new NexusCallerWorkflow(endpointName))));
345+
await worker.ExecuteAsync(async () =>
346+
{
347+
// Start the caller workflow
348+
var handle = await env.Client.StartWorkflowAsync(
349+
(NexusCallerWorkflow wf) => wf.RunAsync("test-input"),
350+
new(id: $"workflow-{Guid.NewGuid()}", taskQueue: taskQueue));
351+
352+
// Manually skip time by 1 day (less than the 2-day delay) and confirm
353+
// the workflow is still running
354+
await env.DelayAsync(TimeSpan.FromDays(1));
355+
var desc = await handle.DescribeAsync();
356+
Assert.Equal(WorkflowExecutionStatus.Running, desc.Status);
357+
358+
// Cancel the caller workflow
359+
await handle.CancelAsync();
360+
var exc = await Assert.ThrowsAsync<WorkflowFailedException>(
361+
() => handle.GetResultAsync());
362+
Assert.IsType<CanceledFailureException>(exc.InnerException);
363+
});
364+
}
365+
366+
[Workflow]
367+
public class NexusCallerWithTimeoutWorkflow
368+
{
369+
private readonly string endpoint;
370+
371+
public NexusCallerWithTimeoutWorkflow(string endpoint) => this.endpoint = endpoint;
372+
373+
[WorkflowRun]
374+
public async Task<string> RunAsync(string input)
375+
{
376+
return await Workflow.CreateNexusClient<INexusLongRunningService>(endpoint).
377+
ExecuteNexusOperationAsync(
378+
svc => svc.RunLongOperation(input),
379+
new() { ScheduleToCloseTimeout = TimeSpan.FromDays(1) });
380+
}
381+
}
382+
383+
[OnlyIntelFact]
384+
public async Task StartTimeSkippingAsync_NexusScheduleToCloseTimeout_TimesOut()
385+
{
386+
await using var env = await WorkflowEnvironment.StartTimeSkippingAsync();
387+
var taskQueue = $"tq-{Guid.NewGuid()}";
388+
var endpointName = $"nexus-endpoint-{taskQueue}";
389+
await env.CreateNexusEndpointAsync(endpointName, taskQueue);
390+
using var worker = new TemporalWorker(
391+
env.Client,
392+
new TemporalWorkerOptions(taskQueue).
393+
AddNexusService(new NexusLongRunningServiceHandler()).
394+
AddWorkflow<NexusLongRunningWorkflow>().
395+
AddWorkflow(WorkflowDefinition.Create(
396+
typeof(NexusCallerWithTimeoutWorkflow),
397+
null,
398+
_args => new NexusCallerWithTimeoutWorkflow(endpointName))));
399+
await worker.ExecuteAsync(async () =>
400+
{
401+
// The backing workflow has a 2-day delay but the Nexus operation has a 1-day
402+
// schedule-to-close timeout, so the timeout should fire via time skipping
403+
var watch = Stopwatch.StartNew();
404+
var exc = await Assert.ThrowsAsync<WorkflowFailedException>(() =>
405+
env.Client.ExecuteWorkflowAsync(
406+
(NexusCallerWithTimeoutWorkflow wf) => wf.RunAsync("test-input"),
407+
new(id: $"workflow-{Guid.NewGuid()}", taskQueue: taskQueue)));
408+
409+
// Verify time was skipped (should complete much faster than 1 day)
410+
Assert.True(watch.Elapsed < TimeSpan.FromSeconds(30));
411+
412+
// Verify the failure chain
413+
var nexusExc = Assert.IsType<NexusOperationFailureException>(exc.InnerException);
414+
var timeoutExc = Assert.IsType<TimeoutFailureException>(nexusExc.InnerException);
415+
Assert.Equal(TimeoutType.ScheduleToClose, timeoutExc.TimeoutType);
416+
});
417+
}
418+
244419
[Fact]
245420
public async Task StartLocal_SearchAttributes_ProperlyRegistered()
246421
{

0 commit comments

Comments
 (0)