Skip to content

Commit

Permalink
Rewrite final Ruby tests (2factorauth#8128)
Browse files Browse the repository at this point in the history
Signed-off-by: Carl <[email protected]>
Co-authored-by: H. Kamran <[email protected]>
  • Loading branch information
Carlgo11 and hkamran80 committed Jul 16, 2024
1 parent f64d96f commit bd7269b
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 170 deletions.
14 changes: 6 additions & 8 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
node-version: '20'

- name: Install dependencies
run: npm install
run: npm install --omit=dev

- name: Get modified files
id: diff
Expand All @@ -50,7 +50,7 @@ jobs:
ALGOLIA_INDEX_NAME: ${{ vars.ALGOLIA_INDEX_NAME }}
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}

- uses: crazy-max/ghaction-import-gpg@v5
- uses: crazy-max/ghaction-import-gpg@v6
id: pgp
with:
gpg_private_key: ${{ secrets.PGP_KEY }}
Expand All @@ -64,17 +64,15 @@ jobs:

- name: Prepare publish directory
run: |
mkdir -p public/icons
cp -r api/. public/
cp -r img/. public/icons/
cp robots.txt public/
rsync -av {robots.txt,api/} public/
rsync -av img/. public/icons/
- uses: actions/upload-pages-artifact@v1
- uses: actions/upload-pages-artifact@v4
with:
path: public/

- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v4

- name: Send webhook to Cloudflare
run: curl -X POST -IL "${{ secrets.WEBHOOK }}" -o /dev/null -w '%{http_code}\n' -s
57 changes: 11 additions & 46 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,6 @@ concurrency:
cancel-in-progress: true

jobs:
ruby-tests:
name: Ruby tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get modified files
id: diff
run: |
echo "::debug:: Fetching files from ${{ github.api_url }}/repos/${{ github.repository }}/pulls/${{ github.event.number }}/files"
FILES=$(curl -s "${{ github.api_url }}/repos/${{ github.repository }}/pulls/${{ github.event.number }}/files" | jq -r '.[] | select(.status != "removed") | .filename' | tr '\n' ' ')
ENTRIES=$(echo "$FILES" | tr ' ' '\n' | grep -E '^entries/.*\.json$' | tr '\n' ' ')
if [ -n "$ENTRIES" ]; then
echo "entries=${ENTRIES}" >> $GITHUB_OUTPUT
fi
IMAGES=$(echo "$FILES" | tr ' ' '\n' | grep -E '^img/.*$' | tr '\n' ' ')
if [ -n "IMAGES" ]; then
echo "images=${IMAGES}" >> $GITHUB_OUTPUT
fi
RUBY=$(echo "$FILES" | tr ' ' '\n' | grep -E '^(script|tests)/.*rb$' | tr '\n' ' ')
if [ -n "RUBY" ]; then
echo "ruby=${RUBY}" >> $GITHUB_OUTPUT
fi
- uses: ruby/setup-ruby@v1
if: ${{ steps.diff.outputs.entries || steps.diff.outputs.images || steps.diff.outputs.ruby }}
with:
bundler-cache: true
ruby-version: '3.0'
env:
BUNDLE_WITH: 'tests'
- name: Validate SVG
if: steps.diff.outputs.images
run: bundle exec ruby ./tests/svg-lint.rb
- name: Validate URL/Domain reachability
if: steps.diff.outputs.entries
run: bundle exec ruby ./tests/validate-urls.rb
continue-on-error: true
- name: Validate Ruby scripts
if: steps.diff.outputs.ruby
run: bundle exec rubocop

node-tests:
name: JavaScript tests
runs-on: ubuntu-latest
Expand All @@ -61,7 +19,7 @@ jobs:
node-version: '20'

- name: Install dependencies
run: npm install
run: npm install --omit=optional

- name: Get modified files
id: diff
Expand All @@ -77,6 +35,10 @@ jobs:
echo "images=${IMAGES}" >> $GITHUB_OUTPUT
fi
- name: Validate JSON structure
if: steps.diff.outputs.entries
run: node tests/json.js ${{ steps.diff.outputs.entries }}

- name: Validate file extensions and permissions
run: tests/validate-fs.sh

Expand All @@ -94,11 +56,14 @@ jobs:

- name: Validate Images
if: ${{ steps.diff.outputs.entries || steps.diff.outputs.images }}
run: node tests/images.js
run: |
node tests/images.js
node tests/svg.js ${{ steps.diff.outputs.images }}
- name: Validate JSON structure
- name: Validate URL reachability
if: steps.diff.outputs.entries
run: node tests/json.js ${{ steps.diff.outputs.entries }}
run: node tests/urls.js ${{ steps.diff.outputs.entries }}
continue-on-error: true

external-tests:
name: External Tests
Expand Down
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.md
*.json
*.yaml
*.yml

entries/
img/
11 changes: 0 additions & 11 deletions Gemfile

This file was deleted.

19 changes: 16 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
{
"name": "twofactorauth",
"private": true,
"scripts": {
"format": "prettier . --write"
},
"dependencies": {
"@actions/core": "^1.10.1",
"dotenv": "^16.4.5",
"glob": "^10.4.1"
},
"optionalDependencies": {
"algoliasearch": "^4.24.0"
},
"devDependencies": {
"@playwright/test": "^1.45.1",
"@xmldom/xmldom": "^0.8.10",
"abort-controller": "^3.0.0",
"ajv": "^8.16.0",
"ajv-errors": "^3.0.0",
"ajv-formats": "^3.0.1",
"algoliasearch": "^4.24.0",
"dotenv": "^16.4.5",
"glob": "^10.4.1"
"prettier": "^3.3.3",
"xml2js": "^0.6.2",
"xpath": "^0.0.34"
}
}
45 changes: 0 additions & 45 deletions tests/svg-lint.rb

This file was deleted.

75 changes: 75 additions & 0 deletions tests/svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const fs = require("fs");
const { DOMParser } = require("xmldom");
const xpath = require("xpath");
const core = require("@actions/core");
let errors = false;

// Function to test the SVG content against an XPath expression
function test(svgContent, xpathExpression) {
try {
const doc = new DOMParser().parseFromString(svgContent, "application/xml");
const nodes = xpath.select(xpathExpression, doc);
return nodes.length > 0;
} catch (err) {
core.error(`Failed to parse SVG content: ${err.message}`);
return false;
}
}

// Function to handle file checking
async function main() {
const files = process.argv.slice(2);

await Promise.allSettled(
files.map(async (file) => {
const error = (msg) => {
core.error(msg, { file });
errors = true;
};

const warn = (msg) => {
core.warning(msg, { file });
};

const svg = fs.readFileSync(file, "utf8");
const doc = new DOMParser().parseFromString(svg, "application/xml");
const parseErrors = doc.getElementsByTagName("parsererror");

if (parseErrors.length > 0) error("Invalid SVG file");
if (svg.includes("<?")) error("Unnecessary processing instruction found");
if (test(svg, "//image")) error("Embedded image detected");
if (svg.split("\n").filter((line) => line.trim()).length > 1)
error("Minimize file to one line");
if (test(svg, "//comment()")) warn("Remove comments");
if (fs.statSync(file).size > 5 * 1024) warn("Unusually large file size");
if (
test(
svg,
'//@*[(starts-with(name(), "data-") or starts-with(name(), "class-"))]',
)
)
warn("Unnecessary data or class attribute");
if (test(svg, "//@width | //@height"))
warn("Use viewBox instead of height/width");
if (test(svg, "/*/@id")) warn("Unnecessary id attribute in root element");
if (test(svg, '//*[@fill="#000" or @fill="#000000"]'))
warn('Unnecessary fill="#000" attribute');
if (test(svg, "//*[@style]"))
warn("Use attributes instead of style elements");
if (test(svg, "//*[@fill-opacity]"))
warn("Use hex color instead of fill-opacity");
if (svg.includes("xml:space"))
warn("Unnecessary XML:space declaration found");
if (
test(
svg,
"//*[@version or @fill-rule or @script or @a or @clipPath or @class]",
)
)
warn("Unnecessary attribute(s) found: version, fill-rule, script, a, clipPath, or class");
}),
);
process.exit(+errors);
}

module.exports = main();
55 changes: 55 additions & 0 deletions tests/urls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const fs = require("fs").promises;
const core = require("@actions/core");
const AbortController = require("abort-controller");

// Helper function to create a timeout promise
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error("timeout")), ms),
);
}

async function checkURL(url, file) {
const controller = new AbortController();
const signal = controller.signal;

try {
const res = await Promise.race([
fetch(url, {
headers: {
"User-Agent":
"2factorauth/URLValidator (+https://2fa.directory/bots)",
},
signal,
}),
timeout(2000).then(() => controller.abort()),
]);

if (res.ok) return true;
else if (res.status !== 403)
core.warning(`Unable to fetch ${url} (${res.status})`, { file });
} catch (e) {
core.warning(`Unable to fetch ${url}`, { file });
}
return false;
}

async function main(files) {
await Promise.all(
files.map(async (file) => {
const json = JSON.parse(await fs.readFile(file));
const entry = json[Object.keys(json)[0]];
let urls = [entry.url ? entry.url : `https://${entry.domain}/`];

entry["additional-domains"]?.forEach((domain) =>
urls.push(`https://${domain}/`),
);

await Promise.all(urls.map((url) => checkURL(url, file)));
}),
);

return true;
}

main(process.argv.slice(2)).then(() => process.exit(0));
2 changes: 1 addition & 1 deletion tests/validate-fs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function checkPerm()
checkExt img svg png
checkExt entries json
checkExt scripts js sh
checkExt tests rb sh json js
checkExt tests sh json js
checkPerm img 664 644
checkPerm tests 775 755 664 644
checkPerm entries 664 644
Expand Down
Loading

0 comments on commit bd7269b

Please sign in to comment.