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
39 changes: 34 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
name: Test

on: push
on: [push, pull_request]

jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
- run: node .yarn/releases/yarn-3.3.0.cjs install --immutable
- run: node .yarn/releases/yarn-3.3.0.cjs test
- run: node .yarn/releases/yarn-3.3.0.cjs exec tsc --noEmit
- run: node .yarn/releases/yarn-3.3.0.cjs build
- run: git diff --exit-code -- dist

test-require-current-artifact:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./
id: strict
continue-on-error: true
with:
require-current-artifact: true
- name: Verify missing artifact fails without release fallback
if: always()
shell: bash
run: test '${{ steps.strict.outcome }}' = 'failure'

upload-artifact:
needs: test-download-release
needs: [unit-tests, test-download-release, test-require-current-artifact]
runs-on: ubuntu-latest
steps:
- name: Download release
Expand All @@ -14,21 +41,21 @@ jobs:
wget -q -O test-server-windows.exe https://github.com/ZNotify/server/releases/download/test/test-server-windows.exe

- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: server-linux
path: |
test-server-linux
if-no-files-found: error
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: server-macos
path: |
test-server-macos
if-no-files-found: error
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: server-windows
path: |
Expand All @@ -46,6 +73,8 @@ jobs:
uses: actions/checkout@v3

- uses: ./
with:
require-current-artifact: true

- name: Test Port
shell: bash
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@
Setup a test server listen on `0.0.0.0:14444`

```yaml
uses: ZNotify/test-action@master
uses: ZNotify/test-server-action@master
```

The action first downloads the current workflow run's server artifact using the
GitHub Actions artifact v4 backend. If no artifact can be downloaded, it uses the
published test release by default, preserving standalone SDK test behavior.

When testing a candidate server build, require its current-run artifact so a
download failure cannot silently substitute the published release:

```yaml
- uses: ZNotify/test-server-action@master
with:
require-current-artifact: true
```

## License

This is free and unencumbered software released into the public domain.
This is free and unencumbered software released into the public domain.
9 changes: 7 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ name: "Setup test server"
author: "Zxilly"
description: "Setup a test server of Notify."

inputs:
require-current-artifact:
description: "Fail if the test server artifact from the current workflow run cannot be downloaded."
required: false
default: "false"

runs:
using: "node16"
using: "node24"
main: "dist/index.js"
post: "dist/post.js"


branding:
icon: "airplay"
color: "green"
color: "green"
343 changes: 287 additions & 56 deletions dist/index.js

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions dist/post.js

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
"author": "Zxilly <zxilly@outlook.com>",
"license": "UNLICENSED",
"scripts": {
"build:main": "esbuild src/main.ts --bundle --platform=node --minify --target=node16 --outfile=dist/index.js",
"build:post": "esbuild src/post.ts --bundle --platform=node --minify --target=node16 --outfile=dist/post.js",
"build:main": "esbuild src/main.ts --bundle --platform=node --minify --target=node24 --outfile=dist/index.js",
"build:post": "esbuild src/post.ts --bundle --platform=node --minify --target=node24 --outfile=dist/post.js",
"build": "yarn build:main && yarn build:post",
"test": "esbuild tests/artifact.test.ts --bundle --platform=node --target=node24 --outfile=.cache/tests/artifact.test.cjs && node --test .cache/tests/artifact.test.cjs",
"update": "yarn build && git add ."
},
"devDependencies": {
"@actions/artifact": "^1.1.1",
"@actions/artifact": "^2.3.2",
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
Expand Down
29 changes: 29 additions & 0 deletions src/artifact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as core from '@actions/core';

export interface ArtifactClient {
getArtifact(name: string): Promise<{ artifact: { id: number; name: string } }>;
downloadArtifact(id: number, options: { path: string }): Promise<{ downloadPath?: string }>;
}

export async function downloadCurrentArtifact(
client: ArtifactClient,
name: string,
path: string,
required: boolean,
): Promise<boolean> {
core.startGroup('Downloading artifact');
try {
const { artifact } = await client.getArtifact(name);
const result = await client.downloadArtifact(artifact.id, { path });
core.info(`Current-run artifact ${artifact.name} (${artifact.id}) downloaded to ${result.downloadPath}`);
return true;
} catch (error) {
if (required) {
throw new Error(`Required current-run artifact ${name} could not be downloaded: ${String(error)}`);
}
core.info('Artifact may not exist, downloading from release');
return false;
} finally {
core.endGroup();
}
}
23 changes: 4 additions & 19 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import axios from 'axios';
import waitPort from 'wait-port';
import fetch from 'node-fetch';
import { cancelTimeout, currentResource, runnerOS, startTimeout } from './util';
import { downloadCurrentArtifact } from './artifact';

const artifactClient = artifact.create();
const artifactClient = new artifact.DefaultArtifactClient();

const res = currentResource;

Expand All @@ -18,7 +19,8 @@ async function run() {
const tempPath = await fs.promises.mkdtemp('server');
core.saveState('tempPath', tempPath);

const da = await downloadArifact(tempPath);
const da = await downloadCurrentArtifact(artifactClient, res.artifactName, tempPath,
core.getBooleanInput('require-current-artifact'));
if (!da) {
await downloadRelease(tempPath);
}
Expand Down Expand Up @@ -114,22 +116,6 @@ async function downloadRelease(tmpDir: string) {
core.endGroup();
}

async function downloadArifact(path: string): Promise<Boolean> {
core.startGroup('Downloading artifact');
try {
const downloadResponse = await artifactClient.downloadArtifact(res.artifactName, path, {
createArtifactFolder: false
});
core.info(`Artifact ${downloadResponse.artifactName} exists. Downloaded to ${downloadResponse.downloadPath}`);
core.endGroup();
return true;
} catch (error) {
core.info('Artifact may not exist, downloading from release');
core.endGroup();
return false;
}
}

async function tryGrantPermission(path: string) {
const filename = res.filename;
core.startGroup('Granting permission');
Expand All @@ -152,4 +138,3 @@ run().catch((error) => {
}).then(() => {
cancelTimeout();
});

4 changes: 4 additions & 0 deletions src/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ async function clean() {
}

async function run() {
if (!core.getState('pid')) {
core.info('No test server was started; skipping cleanup');
return;
}
await log();
await clean();
}
Expand Down
46 changes: 46 additions & 0 deletions tests/artifact.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { ArtifactClient, downloadCurrentArtifact } from '../src/artifact';

for (const required of [false, true]) {
test(`downloads the current-run artifact by ID (required=${required})`, async () => {
const calls: unknown[] = [];
const client: ArtifactClient = {
async getArtifact(name) {
calls.push(['get', name]);
return { artifact: { id: 123, name } };
},
async downloadArtifact(id, options) {
calls.push(['download', id, options]);
return { downloadPath: options.path };
},
};
assert.equal(await downloadCurrentArtifact(client, 'server-linux', '/tmp/server', required), true);
assert.deepEqual(calls, [
['get', 'server-linux'],
['download', 123, { path: '/tmp/server' }],
]);
});
}

for (const stage of ['lookup', 'download']) {
for (const required of [false, true]) {
test(`${stage} failure ${required ? 'rejects strict mode' : 'preserves release fallback'}`, async () => {
const client: ArtifactClient = {
async getArtifact(name) {
if (stage === 'lookup') throw new Error('artifact missing');
return { artifact: { id: 123, name } };
},
async downloadArtifact() {
throw new Error('download failed');
},
};
const result = downloadCurrentArtifact(client, 'server-linux', '/tmp/server', required);
if (required) {
await assert.rejects(result, /Required current-run artifact server-linux could not be downloaded/);
} else {
assert.equal(await result, false);
}
});
}
}
Loading
Loading