@testream/dotnet-reporter 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +200 -110
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,4 +23,4 @@ Run .NET tests and send the results from your codebase to Jira via Testream.
|
|
|
23
23
|
|
|
24
24
|
This installs the reporter, runs your .NET tests, and uploads results to Testream.
|
|
25
25
|
|
|
26
|
-
For CLI options and CI examples, see https://testream.
|
|
26
|
+
For CLI options and CI examples, see https://docs.testream.app/reporters/dotnet.
|
package/dist/index.js
CHANGED
|
@@ -2164,79 +2164,131 @@ module.exports = toNumber;
|
|
|
2164
2164
|
|
|
2165
2165
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
2166
2166
|
exports.detectCIContext = detectCIContext;
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
*/
|
|
2171
|
-
function detectCIContext() {
|
|
2172
|
-
const env = process.env;
|
|
2173
|
-
// GitHub Actions
|
|
2174
|
-
if (env.GITHUB_ACTIONS === 'true') {
|
|
2175
|
-
return {
|
|
2176
|
-
branch: env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME || env.GITHUB_REF?.replace('refs/heads/', ''),
|
|
2177
|
-
commitSha: env.GITHUB_SHA,
|
|
2178
|
-
repositoryUrl: env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY
|
|
2179
|
-
? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`
|
|
2180
|
-
: undefined,
|
|
2181
|
-
buildNumber: env.GITHUB_RUN_NUMBER,
|
|
2182
|
-
buildUrl: env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID
|
|
2183
|
-
? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`
|
|
2184
|
-
: undefined,
|
|
2185
|
-
};
|
|
2167
|
+
function removePrefix(value, prefix) {
|
|
2168
|
+
if (!value) {
|
|
2169
|
+
return undefined;
|
|
2186
2170
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2171
|
+
return value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
|
2172
|
+
}
|
|
2173
|
+
function readEnvironment() {
|
|
2174
|
+
const runtime = globalThis;
|
|
2175
|
+
return runtime.process?.env ?? {};
|
|
2176
|
+
}
|
|
2177
|
+
function buildGithubRepositoryUrl(env) {
|
|
2178
|
+
if (!env.GITHUB_SERVER_URL || !env.GITHUB_REPOSITORY) {
|
|
2179
|
+
return undefined;
|
|
2196
2180
|
}
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
repositoryUrl: env.BUILD_REPOSITORY_URI,
|
|
2203
|
-
buildNumber: env.BUILD_BUILDNUMBER,
|
|
2204
|
-
buildUrl: env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI && env.SYSTEM_TEAMPROJECT && env.BUILD_BUILDID
|
|
2205
|
-
? `${env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${env.BUILD_BUILDID}`
|
|
2206
|
-
: undefined,
|
|
2207
|
-
};
|
|
2181
|
+
return `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`;
|
|
2182
|
+
}
|
|
2183
|
+
function buildGithubBuildUrl(env) {
|
|
2184
|
+
if (!env.GITHUB_SERVER_URL || !env.GITHUB_REPOSITORY || !env.GITHUB_RUN_ID) {
|
|
2185
|
+
return undefined;
|
|
2208
2186
|
}
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
repositoryUrl: env.CIRCLE_REPOSITORY_URL,
|
|
2215
|
-
buildNumber: env.CIRCLE_BUILD_NUM,
|
|
2216
|
-
buildUrl: env.CIRCLE_BUILD_URL,
|
|
2217
|
-
};
|
|
2187
|
+
return `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`;
|
|
2188
|
+
}
|
|
2189
|
+
function detectGitHubBranch(env) {
|
|
2190
|
+
if (env.GITHUB_HEAD_REF) {
|
|
2191
|
+
return env.GITHUB_HEAD_REF;
|
|
2218
2192
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
return {
|
|
2222
|
-
branch: env.GIT_BRANCH?.replace('origin/', ''),
|
|
2223
|
-
commitSha: env.GIT_COMMIT,
|
|
2224
|
-
repositoryUrl: env.GIT_URL,
|
|
2225
|
-
buildNumber: env.BUILD_NUMBER,
|
|
2226
|
-
buildUrl: env.BUILD_URL,
|
|
2227
|
-
};
|
|
2193
|
+
if (env.GITHUB_REF?.startsWith('refs/heads/')) {
|
|
2194
|
+
return env.GITHUB_REF_NAME ?? removePrefix(env.GITHUB_REF, 'refs/heads/');
|
|
2228
2195
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2196
|
+
return undefined;
|
|
2197
|
+
}
|
|
2198
|
+
function detectGitHubActionsContext(env) {
|
|
2199
|
+
return {
|
|
2200
|
+
branch: detectGitHubBranch(env),
|
|
2201
|
+
commitSha: env.GITHUB_SHA,
|
|
2202
|
+
repositoryUrl: buildGithubRepositoryUrl(env),
|
|
2203
|
+
buildNumber: env.GITHUB_RUN_NUMBER,
|
|
2204
|
+
buildUrl: buildGithubBuildUrl(env),
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
function detectGitLabContext(env) {
|
|
2208
|
+
return {
|
|
2209
|
+
branch: env.CI_COMMIT_BRANCH || env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,
|
|
2210
|
+
commitSha: env.CI_COMMIT_SHA,
|
|
2211
|
+
repositoryUrl: env.CI_PROJECT_URL,
|
|
2212
|
+
buildNumber: env.CI_PIPELINE_IID,
|
|
2213
|
+
buildUrl: env.CI_PIPELINE_URL,
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
function detectAzurePipelinesContext(env) {
|
|
2217
|
+
return {
|
|
2218
|
+
branch: removePrefix(env.BUILD_SOURCEBRANCH, 'refs/heads/'),
|
|
2219
|
+
commitSha: env.BUILD_SOURCEVERSION,
|
|
2220
|
+
repositoryUrl: env.BUILD_REPOSITORY_URI,
|
|
2221
|
+
buildNumber: env.BUILD_BUILDNUMBER,
|
|
2222
|
+
buildUrl: env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI && env.SYSTEM_TEAMPROJECT && env.BUILD_BUILDID
|
|
2223
|
+
? `${env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${env.BUILD_BUILDID}`
|
|
2224
|
+
: undefined,
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
function detectCircleCIContext(env) {
|
|
2228
|
+
return {
|
|
2229
|
+
branch: env.CIRCLE_BRANCH,
|
|
2230
|
+
commitSha: env.CIRCLE_SHA1,
|
|
2231
|
+
repositoryUrl: env.CIRCLE_REPOSITORY_URL,
|
|
2232
|
+
buildNumber: env.CIRCLE_BUILD_NUM,
|
|
2233
|
+
buildUrl: env.CIRCLE_BUILD_URL,
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
function detectJenkinsContext(env) {
|
|
2237
|
+
return {
|
|
2238
|
+
branch: removePrefix(env.GIT_BRANCH, 'origin/'),
|
|
2239
|
+
commitSha: env.GIT_COMMIT,
|
|
2240
|
+
repositoryUrl: env.GIT_URL,
|
|
2241
|
+
buildNumber: env.BUILD_NUMBER,
|
|
2242
|
+
buildUrl: env.BUILD_URL,
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
function detectBitbucketContext(env) {
|
|
2246
|
+
return {
|
|
2247
|
+
branch: env.BITBUCKET_BRANCH,
|
|
2248
|
+
commitSha: env.BITBUCKET_COMMIT,
|
|
2249
|
+
repositoryUrl: env.BITBUCKET_GIT_HTTP_ORIGIN,
|
|
2250
|
+
buildNumber: env.BITBUCKET_BUILD_NUMBER,
|
|
2251
|
+
buildUrl: env.BITBUCKET_REPO_FULL_NAME && env.BITBUCKET_BUILD_NUMBER
|
|
2252
|
+
? `https://bitbucket.org/${env.BITBUCKET_REPO_FULL_NAME}/pipelines/results/${env.BITBUCKET_BUILD_NUMBER}`
|
|
2253
|
+
: undefined,
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
const CI_PROVIDER_DETECTORS = [
|
|
2257
|
+
{
|
|
2258
|
+
isMatch: (env) => env.GITHUB_ACTIONS === 'true',
|
|
2259
|
+
detect: detectGitHubActionsContext,
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
isMatch: (env) => env.GITLAB_CI === 'true',
|
|
2263
|
+
detect: detectGitLabContext,
|
|
2264
|
+
},
|
|
2265
|
+
{
|
|
2266
|
+
isMatch: (env) => env.TF_BUILD === 'True',
|
|
2267
|
+
detect: detectAzurePipelinesContext,
|
|
2268
|
+
},
|
|
2269
|
+
{
|
|
2270
|
+
isMatch: (env) => env.CIRCLECI === 'true',
|
|
2271
|
+
detect: detectCircleCIContext,
|
|
2272
|
+
},
|
|
2273
|
+
{
|
|
2274
|
+
isMatch: (env) => Boolean(env.JENKINS_URL),
|
|
2275
|
+
detect: detectJenkinsContext,
|
|
2276
|
+
},
|
|
2277
|
+
{
|
|
2278
|
+
isMatch: (env) => Boolean(env.BITBUCKET_BUILD_NUMBER),
|
|
2279
|
+
detect: detectBitbucketContext,
|
|
2280
|
+
},
|
|
2281
|
+
];
|
|
2282
|
+
/**
|
|
2283
|
+
* Detect CI context from environment variables
|
|
2284
|
+
* Supports: GitHub Actions, GitLab CI, Azure Pipelines, CircleCI, Jenkins, Bitbucket Pipelines
|
|
2285
|
+
*/
|
|
2286
|
+
function detectCIContext() {
|
|
2287
|
+
const env = readEnvironment();
|
|
2288
|
+
for (const provider of CI_PROVIDER_DETECTORS) {
|
|
2289
|
+
if (provider.isMatch(env)) {
|
|
2290
|
+
return provider.detect(env);
|
|
2291
|
+
}
|
|
2240
2292
|
}
|
|
2241
2293
|
return {};
|
|
2242
2294
|
}
|
|
@@ -2299,6 +2351,7 @@ exports.ensureReportId = ensureReportId;
|
|
|
2299
2351
|
exports.uploadTestRun = uploadTestRun;
|
|
2300
2352
|
exports.mapAttachmentsToTestResults = mapAttachmentsToTestResults;
|
|
2301
2353
|
exports.uploadArtifacts = uploadArtifacts;
|
|
2354
|
+
const ci_detection_1 = __nccwpck_require__(406);
|
|
2302
2355
|
const DEFAULT_API_URL = 'https://test-manager-backend.fly.dev';
|
|
2303
2356
|
function ensureReportId(report) {
|
|
2304
2357
|
if (typeof report.reportId === 'string' && report.reportId.trim().length > 0) {
|
|
@@ -2335,6 +2388,52 @@ function normalizeApiUrl(value) {
|
|
|
2335
2388
|
}
|
|
2336
2389
|
return trimmed.replace(/\/+$/, '');
|
|
2337
2390
|
}
|
|
2391
|
+
function isReportAlreadyExistsConflict(error) {
|
|
2392
|
+
return error.errorCode === 'report_already_exists';
|
|
2393
|
+
}
|
|
2394
|
+
function formatUploadError(error) {
|
|
2395
|
+
const detail = error.detail || error.title || error.rawBody || 'Unknown error';
|
|
2396
|
+
const codeLabel = error.errorCode ? `, code=${error.errorCode}` : '';
|
|
2397
|
+
return `Upload failed (HTTP ${error.statusCode}${codeLabel}): ${detail}`;
|
|
2398
|
+
}
|
|
2399
|
+
async function parseHttpError(response) {
|
|
2400
|
+
const rawBody = await response.text();
|
|
2401
|
+
const contentType = response.headers.get('content-type') || '';
|
|
2402
|
+
if (contentType.toLowerCase().includes('json') && rawBody.trim().length > 0) {
|
|
2403
|
+
try {
|
|
2404
|
+
const parsed = JSON.parse(rawBody);
|
|
2405
|
+
return {
|
|
2406
|
+
statusCode: response.status,
|
|
2407
|
+
title: parsed.title,
|
|
2408
|
+
detail: parsed.detail || parsed.details || parsed.error,
|
|
2409
|
+
errorCode: parsed.errorCode,
|
|
2410
|
+
rawBody,
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
catch {
|
|
2414
|
+
// Fall through to raw body handling
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
return {
|
|
2418
|
+
statusCode: response.status,
|
|
2419
|
+
rawBody,
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
function resolveUploadContext(options) {
|
|
2423
|
+
const ciContext = (0, ci_detection_1.detectCIContext)();
|
|
2424
|
+
return {
|
|
2425
|
+
commitSha: options.commitSha ?? ciContext.commitSha,
|
|
2426
|
+
branch: options.branch ?? ciContext.branch,
|
|
2427
|
+
repositoryUrl: options.repositoryUrl ?? ciContext.repositoryUrl,
|
|
2428
|
+
buildName: options.buildName,
|
|
2429
|
+
buildNumber: options.buildNumber ?? ciContext.buildNumber,
|
|
2430
|
+
buildUrl: options.buildUrl ?? ciContext.buildUrl,
|
|
2431
|
+
testEnvironment: options.testEnvironment,
|
|
2432
|
+
appName: options.appName,
|
|
2433
|
+
appVersion: options.appVersion,
|
|
2434
|
+
testType: options.testType,
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2338
2437
|
/**
|
|
2339
2438
|
* Upload test run to Testream backend API
|
|
2340
2439
|
*/
|
|
@@ -2342,20 +2441,12 @@ async function uploadTestRun(options) {
|
|
|
2342
2441
|
const { report, apiKey } = options;
|
|
2343
2442
|
const apiUrl = resolveApiUrl(options.apiUrl);
|
|
2344
2443
|
const reportId = ensureReportId(report);
|
|
2444
|
+
const uploadContext = resolveUploadContext(options);
|
|
2345
2445
|
// Build type-safe IngestRequest payload
|
|
2346
2446
|
const ingestPayload = {
|
|
2347
2447
|
report,
|
|
2348
2448
|
reportId,
|
|
2349
|
-
|
|
2350
|
-
branch: options.branch,
|
|
2351
|
-
repositoryUrl: options.repositoryUrl,
|
|
2352
|
-
buildName: options.buildName,
|
|
2353
|
-
buildNumber: options.buildNumber,
|
|
2354
|
-
buildUrl: options.buildUrl,
|
|
2355
|
-
testEnvironment: options.testEnvironment,
|
|
2356
|
-
appName: options.appName,
|
|
2357
|
-
appVersion: options.appVersion,
|
|
2358
|
-
testType: options.testType,
|
|
2449
|
+
...uploadContext,
|
|
2359
2450
|
};
|
|
2360
2451
|
// Professional logging
|
|
2361
2452
|
console.log('Uploading test results...');
|
|
@@ -2372,34 +2463,41 @@ async function uploadTestRun(options) {
|
|
|
2372
2463
|
}
|
|
2373
2464
|
catch (error) {
|
|
2374
2465
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2466
|
+
const connectionError = `Connection failed: ${errorMessage}`;
|
|
2467
|
+
console.error(connectionError);
|
|
2375
2468
|
return {
|
|
2376
2469
|
success: false,
|
|
2377
2470
|
reportId,
|
|
2378
|
-
error:
|
|
2471
|
+
error: connectionError,
|
|
2379
2472
|
};
|
|
2380
2473
|
}
|
|
2381
|
-
// Handle 409 (report already exists) - idempotent
|
|
2382
|
-
if (response.status === 409) {
|
|
2383
|
-
console.warn('Report already exists (workflow may have been re-run)');
|
|
2384
|
-
return {
|
|
2385
|
-
success: true,
|
|
2386
|
-
reportId,
|
|
2387
|
-
summary: {
|
|
2388
|
-
passed: report.results.summary.passed,
|
|
2389
|
-
failed: report.results.summary.failed,
|
|
2390
|
-
skipped: report.results.summary.skipped,
|
|
2391
|
-
total: report.results.summary.tests,
|
|
2392
|
-
},
|
|
2393
|
-
alreadyExists: true,
|
|
2394
|
-
};
|
|
2395
|
-
}
|
|
2396
|
-
// Handle other errors
|
|
2397
2474
|
if (!response.ok) {
|
|
2398
|
-
const
|
|
2475
|
+
const parsedError = await parseHttpError(response);
|
|
2476
|
+
if (response.status === 409 && isReportAlreadyExistsConflict(parsedError)) {
|
|
2477
|
+
console.warn('Report already exists (workflow may have been re-run)');
|
|
2478
|
+
return {
|
|
2479
|
+
success: true,
|
|
2480
|
+
reportId,
|
|
2481
|
+
summary: {
|
|
2482
|
+
passed: report.results.summary.passed,
|
|
2483
|
+
failed: report.results.summary.failed,
|
|
2484
|
+
skipped: report.results.summary.skipped,
|
|
2485
|
+
total: report.results.summary.tests,
|
|
2486
|
+
},
|
|
2487
|
+
alreadyExists: true,
|
|
2488
|
+
statusCode: parsedError.statusCode,
|
|
2489
|
+
errorCode: parsedError.errorCode,
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
const formattedError = formatUploadError(parsedError);
|
|
2493
|
+
console.error(formattedError);
|
|
2399
2494
|
return {
|
|
2400
2495
|
success: false,
|
|
2401
2496
|
reportId,
|
|
2402
|
-
error:
|
|
2497
|
+
error: formattedError,
|
|
2498
|
+
statusCode: parsedError.statusCode,
|
|
2499
|
+
errorCode: parsedError.errorCode,
|
|
2500
|
+
errorDetail: parsedError.detail || parsedError.rawBody,
|
|
2403
2501
|
};
|
|
2404
2502
|
}
|
|
2405
2503
|
// Success
|
|
@@ -2958,26 +3056,18 @@ async function main() {
|
|
|
2958
3056
|
logger.info('='.repeat(60));
|
|
2959
3057
|
return;
|
|
2960
3058
|
}
|
|
2961
|
-
// Detect CI context from environment
|
|
2962
|
-
const ciContext = (0, uploader_1.detectGitContext)();
|
|
2963
|
-
// Merge CLI options with auto-detected CI context (CLI takes precedence)
|
|
2964
|
-
const branch = options.branch || ciContext.branch;
|
|
2965
|
-
const commitSha = options.commitSha || ciContext.commitSha;
|
|
2966
|
-
const repositoryUrl = options.repositoryUrl || ciContext.repositoryUrl;
|
|
2967
|
-
const buildNumber = options.buildNumber || ciContext.buildNumber;
|
|
2968
|
-
const buildUrl = options.buildUrl || ciContext.buildUrl;
|
|
2969
3059
|
// Upload to API
|
|
2970
3060
|
const result = await (0, uploader_1.uploadToApi)({
|
|
2971
3061
|
report,
|
|
2972
3062
|
apiKey: options.apiKey,
|
|
2973
3063
|
// Git context
|
|
2974
|
-
branch,
|
|
2975
|
-
commitSha,
|
|
2976
|
-
repositoryUrl,
|
|
3064
|
+
branch: options.branch,
|
|
3065
|
+
commitSha: options.commitSha,
|
|
3066
|
+
repositoryUrl: options.repositoryUrl,
|
|
2977
3067
|
// Build metadata
|
|
2978
3068
|
buildName: options.buildName,
|
|
2979
|
-
buildNumber,
|
|
2980
|
-
buildUrl,
|
|
3069
|
+
buildNumber: options.buildNumber,
|
|
3070
|
+
buildUrl: options.buildUrl,
|
|
2981
3071
|
// Environment metadata
|
|
2982
3072
|
testEnvironment: options.testEnvironment,
|
|
2983
3073
|
appName: options.appName,
|