@@ -7,13 +7,16 @@ import (
77 "testing"
88 "time"
99
10+ "connectrpc.com/connect"
1011 "github.com/google/uuid"
1112 "github.com/rs/zerolog"
13+ "github.com/stretchr/testify/require"
1214
1315 contracts "github.com/hatchet-dev/hatchet/internal/services/shared/proto/v1"
1416 "github.com/hatchet-dev/hatchet/internal/services/shared/rpcstream"
1517 "github.com/hatchet-dev/hatchet/internal/services/shared/streams"
1618 "github.com/hatchet-dev/hatchet/pkg/analytics"
19+ v1 "github.com/hatchet-dev/hatchet/pkg/repository"
1720 "github.com/hatchet-dev/hatchet/pkg/repository/sqlcv1"
1821)
1922
@@ -201,3 +204,81 @@ func TestDurableTaskReturnsNilOnEOF(t *testing.T) {
201204 t .Fatal ("DurableTask did not return after EOF" )
202205 }
203206}
207+
208+ type fakeFoundTask struct {
209+ v1.Repository
210+ v1.TaskRepository
211+ }
212+
213+ func (f fakeFoundTask ) Tasks () v1.TaskRepository { return f }
214+ func (fakeFoundTask ) GetTaskByExternalId (context.Context , uuid.UUID , uuid.UUID , bool ) (* sqlcv1.FlattenExternalIdsRow , error ) {
215+ return & sqlcv1.FlattenExternalIdsRow {}, nil
216+ }
217+
218+ func TestHandleWaitForNilConditionBaseReturnsInvalidArgument (t * testing.T ) {
219+ d := newTestDispatcher ()
220+ d .repo = fakeFoundTask {}
221+
222+ for _ , c := range []* contracts.DurableEventListenerConditions {
223+ {SleepConditions : []* contracts.SleepMatchCondition {{}}},
224+ {UserEventConditions : []* contracts.UserEventMatchCondition {{}}},
225+ } {
226+ req := & contracts.DurableTaskWaitForRequest {DurableTaskExternalId : uuid .NewString (), WaitForConditions : c }
227+ err := d .handleWaitFor (context .Background (), & durableTaskInvocation {}, req )
228+ require .Equal (t , connect .CodeInvalidArgument , connect .CodeOf (err ), err )
229+ }
230+ }
231+
232+ type panickingRepo struct {
233+ v1.Repository
234+ calls chan struct {}
235+ }
236+
237+ func (r panickingRepo ) Tasks () v1.TaskRepository {
238+ r .calls <- struct {}{}
239+ panic ("boom" )
240+ }
241+
242+ func TestDurableTaskRecoversFromHandlerPanics (t * testing.T ) {
243+ d := newTestDispatcher ()
244+ calls := make (chan struct {}, 3 )
245+ d .repo = panickingRepo {calls : calls }
246+
247+ taskId := uuid .NewString ()
248+ memo := & contracts.DurableTaskRequest {Message : & contracts.DurableTaskRequest_Memo {
249+ Memo : & contracts.DurableTaskMemoRequest {DurableTaskExternalId : taskId },
250+ }}
251+ status := & contracts.DurableTaskRequest {Message : & contracts.DurableTaskRequest_WorkerStatus {
252+ WorkerStatus : & contracts.DurableTaskWorkerStatusRequest {
253+ WaitingEntries : []* contracts.DurableTaskAwaitedCompletedEntry {{DurableTaskExternalId : taskId }},
254+ },
255+ }}
256+
257+ reqs := []* contracts.DurableTaskRequest {memo , status , status }
258+ server := & fakeDurableTaskServer {
259+ ctx : tenantContext (),
260+ recv : func () (* contracts.DurableTaskRequest , error ) {
261+ // wait for the previous request's handler to panic, so the second status needs the
262+ // worker-status runner to have survived the first
263+ if len (reqs ) < 3 {
264+ <- calls
265+ }
266+
267+ if len (reqs ) == 0 {
268+ return nil , io .EOF
269+ }
270+
271+ req := reqs [0 ]
272+ reqs = reqs [1 :]
273+
274+ return req , nil
275+ },
276+ }
277+
278+ select {
279+ case err := <- runDurableTask (d , server ):
280+ require .NoError (t , err )
281+ case <- time .After (5 * time .Second ):
282+ t .Fatal ("a request after a panicking one was never handled" )
283+ }
284+ }
0 commit comments