Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions .github/workflows/dts-e2e-tests.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: 🧪 DTS Emulator E2E Tests

# This workflow runs E2E tests against the Durable Task Scheduler (DTS) emulator.
# It mirrors the Python testing setup at durabletask-python for Azure-managed tests.
# Tests are split across parallel jobs with separate task hubs for isolation.

on:
push:
Expand All @@ -20,6 +20,16 @@ jobs:
fail-fast: false
matrix:
node-version: ["22.x", "24.x"]
test-group:
- name: "entity"
pattern: "test/e2e-azuremanaged/entity.spec.ts"
- name: "orchestration"
pattern: "test/e2e-azuremanaged/orchestration.spec.ts"
- name: "query-restart"
pattern: "test/e2e-azuremanaged/query-apis.spec.ts test/e2e-azuremanaged/restart.spec.ts"
- name: "retry-history-rewind"
pattern: "test/e2e-azuremanaged/retry-handler.spec.ts test/e2e-azuremanaged/retry-advanced.spec.ts test/e2e-azuremanaged/history.spec.ts test/e2e-azuremanaged/rewind.spec.ts"
name: "e2e (${{ matrix.test-group.name }}, node ${{ matrix.node-version }})"
env:
EMULATOR_VERSION: "latest"
runs-on: ubuntu-latest
Expand All @@ -36,7 +46,7 @@ jobs:
docker run --name dtsemulator -d -p 8080:8080 mcr.microsoft.com/dts/dts-emulator:$EMULATOR_VERSION

- name: ⏳ Wait for container to be ready
run: sleep 10 # Adjust if your service needs more time to start
run: sleep 10

- name: 🔧 Set environment variables
run: |
Expand All @@ -50,7 +60,8 @@ jobs:
registry-url: "https://registry.npmjs.org"

- name: ⚙️ Install dependencies
run: npm install
run: npm ci

- name: ✅ Run E2E tests against DTS emulator
run: npm run test:e2e:azuremanaged:internal
- name: ✅ Run E2E tests — ${{ matrix.test-group.name }}
run: npx jest ${{ matrix.test-group.pattern }} --runInBand --detectOpenHandles
timeout-minutes: 15
6 changes: 3 additions & 3 deletions .github/workflows/pr-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest

env:
NODE_VER: 18.x
NODE_VER: 22.x

steps:
- name: 📥 Checkout code
Expand All @@ -29,7 +29,7 @@ jobs:
registry-url: "https://registry.npmjs.org"

- name: ⚙️ Install dependencies
run: npm install
run: npm ci

- name: 🔍 Run linting
run: npm run lint
Expand All @@ -56,7 +56,7 @@ jobs:
registry-url: "https://registry.npmjs.org"

- name: ⚙️ Install dependencies
run: npm install
run: npm ci

# Install Go SDK for durabletask-go sidecar
- name: 🔧 Install Go SDK
Expand Down
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test:e2e": "./scripts/test-e2e.sh",
"test:e2e:internal": "jest tests/e2e --runInBand --detectOpenHandles",
"test:e2e:one": "jest tests/e2e --runInBand --detectOpenHandles --testNamePattern",
"test:e2e:azuremanaged:internal": "jest test/e2e-azuremanaged --runInBand --detectOpenHandles",
"test:e2e:azuremanaged:internal": "jest test/e2e-azuremanaged --detectOpenHandles",
"test:e2e:azuremanaged": "./scripts/test-e2e-azuremanaged.sh",
"lint": "eslint .",
"pretty": "prettier --list-different \"**/*.{ts,tsx,js,jsx,json,md}\"",
Expand Down
29 changes: 6 additions & 23 deletions packages/durabletask-js-azuremanaged/src/credential-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,34 +39,17 @@ export function getCredentialFromAuthenticationType(
}

case "workloadidentity": {
const options: {
clientId?: string;
tenantId?: string;
tokenFilePath?: string;
additionallyAllowedTenants?: string[];
} = {};

const clientId = connectionString.getClientId();
if (clientId) {
options.clientId = clientId;
}

const tenantId = connectionString.getTenantId();
if (tenantId) {
options.tenantId = tenantId;
}

const tokenFilePath = connectionString.getTokenFilePath();
if (tokenFilePath) {
options.tokenFilePath = tokenFilePath;
}

const additionallyAllowedTenants = connectionString.getAdditionallyAllowedTenants();
if (additionallyAllowedTenants) {
options.additionallyAllowedTenants = additionallyAllowedTenants;
}

return new WorkloadIdentityCredential(options);
return new WorkloadIdentityCredential({
...(clientId && { clientId }),
...(tenantId && { tenantId }),
...(tokenFilePath && { tokenFilePath }),
...(additionallyAllowedTenants && { additionallyAllowedTenants }),
});
}

case "environment":
Expand Down
4 changes: 2 additions & 2 deletions packages/durabletask-js-azuremanaged/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ abstract class DurableTaskAzureManagedOptionsBase {

// Add https:// prefix if no protocol is specified
if (!endpoint.startsWith("http://") && !endpoint.startsWith("https://")) {
endpoint = "https://" + endpoint;
endpoint = `https://${endpoint}`;
}

try {
const url = new URL(endpoint);
let authority = url.hostname;
if (url.port) {
authority += ":" + url.port;
authority = `${authority}:${url.port}`;
}
return authority;
} catch {
Expand Down
2 changes: 1 addition & 1 deletion packages/durabletask-js/src/orchestration/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class Page<T> {
* Returns true if there are more pages available.
*/
get hasMoreResults(): boolean {
return this.continuationToken !== undefined && this.continuationToken !== "";
return !!this.continuationToken;
}
}

Expand Down
70 changes: 37 additions & 33 deletions packages/durabletask-js/src/tracing/trace-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,33 +517,27 @@ export function processActionsForTracing(
}
}

/**
* Emits a span for calling an entity from an orchestration (request/response).
*
* @param orchestrationSpan - The parent orchestration span.
* @param operationName - The entity operation name.
* @param targetInstanceId - The target entity instance ID.
* @param taskId - The sequential task ID.
*/
export function emitSpanForEntityCall(
function emitSpanForEntityOperation(
orchestrationSpan: Span,
operationName: string,
taskType: string,
getSpanKind: (otel: any) => any,
targetInstanceId?: string,
taskId?: number,
): void {
const otel = getOtelApi();
const tracer = getTracer();
if (!otel || !tracer) return;

const spanName = createSpanName(TaskType.CALL_ENTITY, operationName);
const spanName = createSpanName(taskType, operationName);
const parentContext = otel.trace.setSpan(otel.context.active(), orchestrationSpan);

const span = tracer.startSpan(
spanName,
{
kind: otel.SpanKind.CLIENT,
kind: getSpanKind(otel),
attributes: {
[DurableTaskAttributes.TYPE]: TaskType.CALL_ENTITY,
[DurableTaskAttributes.TYPE]: taskType,
[DurableTaskAttributes.ENTITY_OPERATION]: operationName,
...(targetInstanceId ? { [DurableTaskAttributes.ENTITY_INSTANCE_ID]: targetInstanceId } : {}),
...(taskId !== undefined ? { [DurableTaskAttributes.TASK_TASK_ID]: taskId } : {}),
Expand All @@ -555,6 +549,30 @@ export function emitSpanForEntityCall(
span.end();
}

/**
* Emits a span for calling an entity from an orchestration (request/response).
*
* @param orchestrationSpan - The parent orchestration span.
* @param operationName - The entity operation name.
* @param targetInstanceId - The target entity instance ID.
* @param taskId - The sequential task ID.
*/
export function emitSpanForEntityCall(
orchestrationSpan: Span,
operationName: string,
targetInstanceId?: string,
taskId?: number,
): void {
emitSpanForEntityOperation(
orchestrationSpan,
operationName,
TaskType.CALL_ENTITY,
(otel) => otel.SpanKind.CLIENT,
targetInstanceId,
taskId,
);
}

/**
* Emits a span for signaling an entity from an orchestration (fire-and-forget).
*
Expand All @@ -569,28 +587,14 @@ export function emitSpanForEntitySignal(
targetInstanceId?: string,
taskId?: number,
): void {
const otel = getOtelApi();
const tracer = getTracer();
if (!otel || !tracer) return;

const spanName = createSpanName(TaskType.SIGNAL_ENTITY, operationName);
const parentContext = otel.trace.setSpan(otel.context.active(), orchestrationSpan);

const span = tracer.startSpan(
spanName,
{
kind: otel.SpanKind.PRODUCER,
attributes: {
[DurableTaskAttributes.TYPE]: TaskType.SIGNAL_ENTITY,
[DurableTaskAttributes.ENTITY_OPERATION]: operationName,
...(targetInstanceId ? { [DurableTaskAttributes.ENTITY_INSTANCE_ID]: targetInstanceId } : {}),
...(taskId !== undefined ? { [DurableTaskAttributes.TASK_TASK_ID]: taskId } : {}),
},
},
parentContext,
emitSpanForEntityOperation(
orchestrationSpan,
operationName,
TaskType.SIGNAL_ENTITY,
(otel) => otel.SpanKind.PRODUCER,
targetInstanceId,
taskId,
);

span.end();
}

/**
Expand Down
Loading
Loading