diff --git a/.github/helper-bot/.gitignore b/.github/helper-bot/.gitignore
index eec56d637..7c1100e4d 100644
--- a/.github/helper-bot/.gitignore
+++ b/.github/helper-bot/.gitignore
@@ -1,3 +1,4 @@
node_modules
package-lock.json
-artifacts
\ No newline at end of file
+artifacts
+.nyc_output/
\ No newline at end of file
diff --git a/.github/helper-bot/index.js b/.github/helper-bot/index.js
index 8f2501ee8..9769c6bd8 100644
--- a/.github/helper-bot/index.js
+++ b/.github/helper-bot/index.js
@@ -1,15 +1,66 @@
const fs = require('fs')
const cp = require('child_process')
-const github = require('gh-helpers')()
const pcManifestURL = 'https://launchermeta.mojang.com/mc/game/version_manifest.json'
const changelogURL = 'https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs'
+// Initialize github helper - can be mocked for testing
+let github
+try {
+ github = require('gh-helpers')()
+} catch (e) {
+ // For testing environment, create mock
+ github = {
+ mock: true,
+ createPullRequest: () => {},
+ sendWorkflowDispatch: () => {},
+ findIssue: () => {},
+ close: () => {},
+ createIssue: () => {}
+ }
+}
+
+// Testable functions
function exec (file, args, options = {}) {
const opts = { stdio: 'inherit', ...options }
console.log('> ', file, args.join(' '), options.cwd ? `(cwd: ${options.cwd})` : '')
return github.mock ? undefined : cp.execFileSync(file, args, opts)
}
-const download = (url, dest) => exec('curl', ['-L', url, '-o', dest])
+
+function download(url, dest) {
+ return exec('curl', ['-L', url, '-o', dest])
+}
+
+function sanitizeVersion(version) {
+ return version?.replace(/[^a-zA-Z0-9_.]/g, '_')
+}
+
+function generateBranchName(edition, version) {
+ const branchNameVersion = version.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()
+ return `${edition}-${branchNameVersion}`
+}
+
+function createPRBody(edition, version, issueUrl, protocolVersion, branchName) {
+ return `
+This automated PR sets up the relevant boilerplate for Minecraft ${edition} version ${version}. Fixes ${issueUrl}.
+
+Related:
+- Issue: ${issueUrl}
+- Protocol Version: ${protocolVersion}
+
+
+* You can help contribute to this PR by opening a PR against this ${branchName} branch instead of master.
+`
+}
+
+function createWorkflowDispatch(repo, workflow, inputs) {
+ return {
+ owner: 'PrismarineJS',
+ repo,
+ workflow,
+ branch: repo === 'minecraft-data-generator' ? 'main' : 'master',
+ inputs
+ }
+}
function buildFirstIssue (title, result, jarData) {
const protocolVersion = jarData?.protocol_version || 'Failed to obtain from JAR'
@@ -43,8 +94,7 @@ async function createInitialPull (edition, issueUrl, { version, protocolVersion
exec('npm', ['install'], { cwd: 'tools/js' })
exec('npm', ['run', 'version', edition, version, protocolVersion], { cwd: 'tools/js' })
exec('npm', ['run', 'build'], { cwd: 'tools/js' })
- const branchNameVersion = version.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()
- const branchName = `${edition}-${branchNameVersion}`
+ const branchName = generateBranchName(edition, version)
const title = `🎈 Add Minecraft ${edition} ${version} data`
// First, delete any existing branch
try {
@@ -58,16 +108,7 @@ async function createInitialPull (edition, issueUrl, { version, protocolVersion
exec('git', ['add', '--all'])
exec('git', ['commit', '-m', title])
exec('git', ['push', 'origin', branchName, '--force'])
- const body = `
-This automated PR sets up the relevant boilerplate for Minecraft ${edition} version ${version}. Fixes ${issueUrl}.
-
-Related:
-- Issue: ${issueUrl}
-- Protocol Version: ${protocolVersion}
-
-
-* You can help contribute to this PR by opening a PR against this ${branchName} branch instead of master.
-`
+ const body = createPRBody(edition, version, issueUrl, protocolVersion, branchName)
const pr = await github.createPullRequest(title, body, branchName, 'master')
pr.branchName = branchName
return pr
@@ -151,31 +192,19 @@ async function updateManifestPC () {
})
console.log('Created PR', pr)
// Ask minecraft-data-generator to handle new update
- const dispatchPayload = {
- owner: 'PrismarineJS',
- repo: 'minecraft-data-generator',
- workflow: 'handle-mcdata-update.yml',
- branch: 'main',
- inputs: {
- version: latestVersion,
- issue_number: issue?.number,
- pr_number: pr?.number
- }
- }
+ const dispatchPayload = createWorkflowDispatch('minecraft-data-generator', 'handle-mcdata-update.yml', {
+ version: latestVersion,
+ issue_number: issue?.number,
+ pr_number: pr?.number
+ })
console.log('Sending workflow dispatch', dispatchPayload)
await github.sendWorkflowDispatch(dispatchPayload)
// Ask node-minecraft-protocol to handle new update
- const nodeDispatchPayload = {
- owner: 'PrismarineJS',
- repo: 'node-minecraft-protocol',
- workflow: 'update-from-minecraft-data.yml',
- branch: 'master',
- inputs: {
- new_mc_version: latestVersion,
- mcdata_branch: pr.branchName,
- mcdata_pr_url: pr.url
- }
- }
+ const nodeDispatchPayload = createWorkflowDispatch('node-minecraft-protocol', 'update-from-minecraft-data.yml', {
+ new_mc_version: latestVersion,
+ mcdata_branch: pr.branchName,
+ mcdata_pr_url: pr.url
+ })
console.log('Sending workflow dispatch', nodeDispatchPayload)
await github.sendWorkflowDispatch(nodeDispatchPayload)
// node-minecraft-protocol would then dispatch to mineflayer
@@ -233,4 +262,19 @@ async function updateManifestPC () {
}
}
-updateManifestPC()
+// Export for testing
+module.exports = {
+ sanitizeVersion,
+ buildFirstIssue,
+ generateBranchName,
+ createPRBody,
+ createWorkflowDispatch,
+ createInitialPull,
+ updateManifestPC,
+ setGithub: (mockGithub) => { github = mockGithub }
+}
+
+// Run main function if called directly
+if (require.main === module) {
+ updateManifestPC()
+}
diff --git a/.github/helper-bot/package.json b/.github/helper-bot/package.json
index ee4b17df1..778af5d5f 100644
--- a/.github/helper-bot/package.json
+++ b/.github/helper-bot/package.json
@@ -1,11 +1,25 @@
{
+ "name": "minecraft-data-helper-bot",
+ "version": "1.0.0",
+ "description": "Helper bot for minecraft-data automation",
+ "main": "index.js",
"scripts": {
- "fix": "standard --fix"
+ "test": "mocha test/**/*.test.js",
+ "test:watch": "mocha test/**/*.test.js --watch",
+ "test:coverage": "nyc mocha test/**/*.test.js"
+ },
+ "devDependencies": {
+ "mocha": "^10.2.0",
+ "sinon": "^17.0.1",
+ "nyc": "^15.1.0"
},
"dependencies": {
- "gh-helpers": "^1.0.0"
+ "gh-helpers": "*"
},
- "devDependencies": {
- "standard": "^17.1.2"
- }
+ "directories": {
+ "test": "test"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
}
diff --git a/.github/helper-bot/test/index.test.js b/.github/helper-bot/test/index.test.js
new file mode 100644
index 000000000..756d53a25
--- /dev/null
+++ b/.github/helper-bot/test/index.test.js
@@ -0,0 +1,199 @@
+const sinon = require('sinon')
+const fs = require('fs')
+const assert = require('assert')
+const path = require('path')
+
+// Import the actual implementation
+const helperBot = require('../index')
+
+describe('Minecraft Data Helper Bot', function() {
+ let fsStub
+
+ beforeEach(function() {
+ fsStub = sinon.stub(fs, 'readFileSync')
+ })
+
+ afterEach(function() {
+ sinon.restore()
+ })
+
+ describe('sanitizeVersion', function() {
+ it('should sanitize version strings correctly', function() {
+ const testCases = [
+ { input: '1.21.9', expected: '1.21.9' },
+ { input: '1.21.9-test', expected: '1.21.9_test' },
+ { input: '24w01a', expected: '24w01a' },
+ { input: 'invalid!@#$%^&*()', expected: 'invalid__________' },
+ { input: undefined, expected: undefined }
+ ]
+
+ testCases.forEach(({ input, expected }) => {
+ const result = helperBot.sanitizeVersion(input)
+ assert.strictEqual(result, expected, `sanitizeVersion('${input}') should return '${expected}'`)
+ })
+ })
+ })
+
+ describe('generateBranchName', function() {
+ it('should create correct branch names', function() {
+ const testCases = [
+ { edition: 'pc', version: '1.21.9', expected: 'pc-1_21_9' },
+ { edition: 'bedrock', version: '1.21.9-test', expected: 'bedrock-1_21_9_test' },
+ { edition: 'pc', version: '24w01a', expected: 'pc-24w01a' },
+ { edition: 'pc', version: 'test!@#', expected: 'pc-test___' }
+ ]
+
+ testCases.forEach(({ edition, version, expected }) => {
+ const result = helperBot.generateBranchName(edition, version)
+ assert.strictEqual(result, expected, `generateBranchName('${edition}', '${version}') should return '${expected}'`)
+ })
+ })
+ })
+
+ describe('buildFirstIssue', function() {
+ it('should create correct issue format', function() {
+ const title = 'Support Minecraft PC 1.21.9'
+ const result = {
+ id: '1.21.9',
+ type: 'release',
+ releaseTime: '2024-01-01T00:00:00Z'
+ }
+ const jarData = {
+ protocol_version: 767,
+ name: 'Minecraft 1.21.9',
+ world_version: 3955,
+ java_version: 21
+ }
+
+ const issue = helperBot.buildFirstIssue(title, result, jarData)
+
+ assert.strictEqual(issue.title, title)
+ assert(issue.body.includes('version **1.21.9**'), 'Should contain version')
+ assert(issue.body.includes('Protocol ID