|
7 | 7 |
|
8 | 8 | "github.com/hookdeck/outpost/cmd/e2e/httpclient" |
9 | 9 | "github.com/hookdeck/outpost/internal/idgen" |
| 10 | + "github.com/stretchr/testify/require" |
10 | 11 | ) |
11 | 12 |
|
12 | 13 | // TestingT is an interface wrapper around *testing.T |
@@ -1388,3 +1389,104 @@ func (suite *basicSuite) TestDestwebhookFilter() { |
1388 | 1389 | } |
1389 | 1390 | suite.RunAPITests(suite.T(), tests) |
1390 | 1391 | } |
| 1392 | + |
| 1393 | +// TestDeliveryRetry tests that failed deliveries are scheduled for retry via RSMQ. |
| 1394 | +// This exercises the RSMQ Lua scripts that are known to fail with Dragonfly. |
| 1395 | +func (suite *basicSuite) TestDeliveryRetry() { |
| 1396 | + t := suite.T() |
| 1397 | + tenantID := idgen.String() |
| 1398 | + destinationID := idgen.Destination() |
| 1399 | + secret := "testsecret1234567890abcdefghijklmnop" |
| 1400 | + |
| 1401 | + // Setup: create tenant |
| 1402 | + resp, err := suite.client.Do(suite.AuthRequest(httpclient.Request{ |
| 1403 | + Method: httpclient.MethodPUT, |
| 1404 | + Path: "/" + tenantID, |
| 1405 | + })) |
| 1406 | + require.NoError(t, err) |
| 1407 | + require.Equal(t, http.StatusCreated, resp.StatusCode) |
| 1408 | + |
| 1409 | + // Setup: configure mock server destination |
| 1410 | + resp, err = suite.client.Do(httpclient.Request{ |
| 1411 | + Method: httpclient.MethodPUT, |
| 1412 | + BaseURL: suite.mockServerBaseURL, |
| 1413 | + Path: "/destinations", |
| 1414 | + Body: map[string]interface{}{ |
| 1415 | + "id": destinationID, |
| 1416 | + "type": "webhook", |
| 1417 | + "config": map[string]interface{}{ |
| 1418 | + "url": fmt.Sprintf("%s/webhook/%s", suite.mockServerBaseURL, destinationID), |
| 1419 | + }, |
| 1420 | + "credentials": map[string]interface{}{ |
| 1421 | + "secret": secret, |
| 1422 | + }, |
| 1423 | + }, |
| 1424 | + }) |
| 1425 | + require.NoError(t, err) |
| 1426 | + require.Equal(t, http.StatusOK, resp.StatusCode) |
| 1427 | + |
| 1428 | + // Setup: create destination in outpost |
| 1429 | + resp, err = suite.client.Do(suite.AuthRequest(httpclient.Request{ |
| 1430 | + Method: httpclient.MethodPOST, |
| 1431 | + Path: "/" + tenantID + "/destinations", |
| 1432 | + Body: map[string]interface{}{ |
| 1433 | + "id": destinationID, |
| 1434 | + "type": "webhook", |
| 1435 | + "topics": "*", |
| 1436 | + "config": map[string]interface{}{ |
| 1437 | + "url": fmt.Sprintf("%s/webhook/%s", suite.mockServerBaseURL, destinationID), |
| 1438 | + }, |
| 1439 | + "credentials": map[string]interface{}{ |
| 1440 | + "secret": secret, |
| 1441 | + }, |
| 1442 | + }, |
| 1443 | + })) |
| 1444 | + require.NoError(t, err) |
| 1445 | + require.Equal(t, http.StatusCreated, resp.StatusCode) |
| 1446 | + |
| 1447 | + // Publish event with retry enabled and should_err to force failure |
| 1448 | + // This will trigger the RSMQ retry scheduler |
| 1449 | + resp, err = suite.client.Do(suite.AuthRequest(httpclient.Request{ |
| 1450 | + Method: httpclient.MethodPOST, |
| 1451 | + Path: "/publish", |
| 1452 | + Body: map[string]interface{}{ |
| 1453 | + "tenant_id": tenantID, |
| 1454 | + "topic": "user.created", |
| 1455 | + "eligible_for_retry": true, // Enable retry - exercises RSMQ! |
| 1456 | + "metadata": map[string]any{ |
| 1457 | + "should_err": "true", // Force delivery to fail |
| 1458 | + }, |
| 1459 | + "data": map[string]any{ |
| 1460 | + "test": "retry", |
| 1461 | + }, |
| 1462 | + }, |
| 1463 | + })) |
| 1464 | + require.NoError(t, err) |
| 1465 | + require.Equal(t, http.StatusAccepted, resp.StatusCode) |
| 1466 | + |
| 1467 | + // Wait for retry to be scheduled and attempted |
| 1468 | + // RetryIntervalSeconds=1 in test config, so 3s should be enough for at least one retry |
| 1469 | + time.Sleep(3 * time.Second) |
| 1470 | + |
| 1471 | + // Verify: check that multiple delivery attempts were made (original + retries) |
| 1472 | + resp, err = suite.client.Do(httpclient.Request{ |
| 1473 | + Method: httpclient.MethodGET, |
| 1474 | + BaseURL: suite.mockServerBaseURL, |
| 1475 | + Path: "/destinations/" + destinationID + "/events", |
| 1476 | + }) |
| 1477 | + require.NoError(t, err) |
| 1478 | + require.Equal(t, http.StatusOK, resp.StatusCode) |
| 1479 | + |
| 1480 | + events, ok := resp.Body.([]interface{}) |
| 1481 | + require.True(t, ok, "response body should be array") |
| 1482 | + // Should have more than 1 event if retry worked (original attempt + at least 1 retry) |
| 1483 | + require.Greater(t, len(events), 1, "expected multiple delivery attempts (original + retry), got %d", len(events)) |
| 1484 | + |
| 1485 | + // Cleanup |
| 1486 | + resp, err = suite.client.Do(suite.AuthRequest(httpclient.Request{ |
| 1487 | + Method: httpclient.MethodDELETE, |
| 1488 | + Path: "/" + tenantID, |
| 1489 | + })) |
| 1490 | + require.NoError(t, err) |
| 1491 | + require.Equal(t, http.StatusOK, resp.StatusCode) |
| 1492 | +} |
0 commit comments