diff --git a/functions/webhook/parseGitPatch.js b/functions/webhook/parseGitPatch.js
index 1efb961..cd0dacb 100644
--- a/functions/webhook/parseGitPatch.js
+++ b/functions/webhook/parseGitPatch.js
@@ -67,8 +67,8 @@ export function parseGitPatch(patch) {
const [, a, b] = match4
- let nA = parseInt(a)
- let nB = parseInt(b)
+ let nA = parseInt(a) - 1
+ let nB = parseInt(b) - 1
lines.forEach(line => {
nA++
diff --git a/tests/functions/webhook/fixture.leading_blank_line.diff b/tests/functions/webhook/fixture.leading_blank_line.diff
new file mode 100644
index 0000000..f45c6b1
--- /dev/null
+++ b/tests/functions/webhook/fixture.leading_blank_line.diff
@@ -0,0 +1,12 @@
+diff --git a/src/App.tsx b/src/App.tsx
+index 898cc83..eebeccc 100644
+--- a/src/App.tsx
++++ b/src/App.tsx
+@@ -5,6 +5,7 @@ import { useAuthContext } from './hooks/useAuthContext'
+
+ function App() {
+ const { isLoggedIn, login } = useAuthContext()
++ console.log('hello world')
+ return (
+ <>
+
diff --git a/tests/functions/webhook/fixture.nominal_multi_files.diff b/tests/functions/webhook/fixture.nominal_multi_files.diff
new file mode 100644
index 0000000..d3c7754
--- /dev/null
+++ b/tests/functions/webhook/fixture.nominal_multi_files.diff
@@ -0,0 +1,50 @@
+diff --git a/package.json b/package.json
+index babfea0..abf98a6 100644
+--- a/package.json
++++ b/package.json
+@@ -2,7 +2,10 @@
+ "name": "@nearform/quantum",
+ "version": "0.2.0",
+ "description": "Component library based on the Quantum Design System",
+- "main": "dist/index.js",
++ "exports": {
++ ".": "./dist/index.js",
++ "./plugin": "./dist/plugin.js"
++ },
+ "scripts": {
+ "build": "tsup",
+ "lint": "eslint .",
+diff --git a/src/plugin.js b/src/plugin.js
+new file mode 100644
+index 0000000..3f8185c
+--- /dev/null
++++ b/src/plugin.js
+@@ -0,0 +1,10 @@
++const quantumConfig = require('../tailwind.config.js')
++const plugin = require('tailwindcss/plugin')
++
++const quantumPlugin = plugin(() => {}, {
++ theme: {
++ ...quantumConfig.theme
++ }
++})
++
++module.exports = quantumPlugin
+diff --git a/tsup.config.ts b/tsup.config.ts
+index 143fd12..c0e6ed0 100644
+--- a/tsup.config.ts
++++ b/tsup.config.ts
+@@ -1,7 +1,12 @@
+ import { defineConfig } from 'tsup'
+
+ export default defineConfig({
+- entry: ['src/index.ts', 'src/global.css', 'src/colors/index.ts'],
++ entry: [
++ 'src/index.ts',
++ 'src/global.css',
++ 'src/colors/index.ts',
++ 'src/plugin.js'
++ ],
+ dts: { entry: ['src/index.ts'] },
+ clean: true,
+ sourcemap: true
diff --git a/tests/functions/webhook/fixture.nominal_single_file.diff b/tests/functions/webhook/fixture.nominal_single_file.diff
new file mode 100644
index 0000000..314d16d
--- /dev/null
+++ b/tests/functions/webhook/fixture.nominal_single_file.diff
@@ -0,0 +1,19 @@
+diff --git a/functions/webhook/triggerReviews.js b/functions/webhook/triggerReviews.js
+index 316c3a5..2b2e758 100644
+--- a/functions/webhook/triggerReviews.js
++++ b/functions/webhook/triggerReviews.js
+@@ -85,7 +85,13 @@ export function shouldTriggerLLMReview(payload) {
+ }
+
+ export function shouldTriggerRuleBasedReview(payload) {
+- return payload.pull_request && payload.action === 'ready_for_review'
++ const pullRequest = payload.pull_request
++ return (
++ pullRequest &&
++ pullRequest.user.type === 'User' &&
++ (payload.action === 'ready_for_review' ||
++ (payload.action === 'opened' && pullRequest.draft === false))
++ )
+ }
+
+ export async function triggerLLMReview(context) {
diff --git a/tests/functions/webhook/parseGitPatch.test.js b/tests/functions/webhook/parseGitPatch.test.js
new file mode 100644
index 0000000..f135603
--- /dev/null
+++ b/tests/functions/webhook/parseGitPatch.test.js
@@ -0,0 +1,242 @@
+import { describe, test } from 'node:test'
+import assert from 'node:assert'
+import { readFileSync } from 'node:fs'
+import parseGitPatch from '../../../functions/webhook/parseGitPatch.js'
+
+import path from 'path'
+import url from 'url'
+const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
+
+describe('Parsing git patches', () => {
+ test('nominal - single file', () => {
+ const rawGitPatch = readFileSync(
+ path.join(__dirname, 'fixture.nominal_single_file.diff'),
+ 'utf8'
+ )
+ const parsedGitPatch = parseGitPatch(rawGitPatch)
+
+ assert.deepEqual(parsedGitPatch, {
+ files: [
+ {
+ added: false,
+ deleted: false,
+ beforeName: 'functions/webhook/triggerReviews.js',
+ afterName: 'functions/webhook/triggerReviews.js',
+ modifiedLines: [
+ {
+ added: false,
+ lineNumber: 88,
+ line: " return payload.pull_request && payload.action === 'ready_for_review'"
+ },
+ {
+ added: true,
+ lineNumber: 88,
+ line: ' const pullRequest = payload.pull_request'
+ },
+ {
+ added: true,
+ lineNumber: 89,
+ line: ' return ('
+ },
+ {
+ added: true,
+ lineNumber: 90,
+ line: ' pullRequest &&'
+ },
+ {
+ added: true,
+ lineNumber: 91,
+ line: " pullRequest.user.type === 'User' &&"
+ },
+ {
+ added: true,
+ lineNumber: 92,
+ line: " (payload.action === 'ready_for_review' ||"
+ },
+ {
+ added: true,
+ lineNumber: 93,
+ line: " (payload.action === 'opened' && pullRequest.draft === false))"
+ },
+ {
+ added: true,
+ lineNumber: 94,
+ line: ' )'
+ }
+ ]
+ }
+ ]
+ })
+ })
+
+ test('nominal - multi files', () => {
+ const rawGitPatch = readFileSync(
+ path.join(__dirname, 'fixture.nominal_multi_files.diff'),
+ 'utf8'
+ )
+ const parsedGitPatch = parseGitPatch(rawGitPatch)
+
+ assert.deepEqual(parsedGitPatch, {
+ files: [
+ {
+ added: false,
+ deleted: false,
+ beforeName: 'package.json',
+ afterName: 'package.json',
+ modifiedLines: [
+ {
+ added: false,
+ lineNumber: 5,
+ line: ' "main": "dist/index.js",'
+ },
+ {
+ added: true,
+ lineNumber: 5,
+ line: ' "exports": {'
+ },
+ {
+ added: true,
+ lineNumber: 6,
+ line: ' ".": "./dist/index.js",'
+ },
+ {
+ added: true,
+ lineNumber: 7,
+ line: ' "./plugin": "./dist/plugin.js"'
+ },
+ {
+ added: true,
+ lineNumber: 8,
+ line: ' },'
+ }
+ ]
+ },
+ {
+ added: true,
+ deleted: false,
+ beforeName: 'src/plugin.js',
+ afterName: 'src/plugin.js',
+ modifiedLines: [
+ {
+ added: true,
+ lineNumber: 1,
+ line: "const quantumConfig = require('../tailwind.config.js')"
+ },
+ {
+ added: true,
+ lineNumber: 2,
+ line: "const plugin = require('tailwindcss/plugin')"
+ },
+ {
+ added: true,
+ lineNumber: 3,
+ line: ''
+ },
+ {
+ added: true,
+ lineNumber: 4,
+ line: 'const quantumPlugin = plugin(() => {}, {'
+ },
+ {
+ added: true,
+ lineNumber: 5,
+ line: ' theme: {'
+ },
+ {
+ added: true,
+ lineNumber: 6,
+ line: ' ...quantumConfig.theme'
+ },
+ {
+ added: true,
+ lineNumber: 7,
+ line: ' }'
+ },
+ {
+ added: true,
+ lineNumber: 8,
+ line: '})'
+ },
+ {
+ added: true,
+ lineNumber: 9,
+ line: ''
+ },
+ {
+ added: true,
+ lineNumber: 10,
+ line: 'module.exports = quantumPlugin'
+ }
+ ]
+ },
+ {
+ added: false,
+ deleted: false,
+ beforeName: 'tsup.config.ts',
+ afterName: 'tsup.config.ts',
+ modifiedLines: [
+ {
+ added: false,
+ lineNumber: 4,
+ line: " entry: ['src/index.ts', 'src/global.css', 'src/colors/index.ts'],"
+ },
+ {
+ added: true,
+ lineNumber: 4,
+ line: ' entry: ['
+ },
+ {
+ added: true,
+ lineNumber: 5,
+ line: " 'src/index.ts',"
+ },
+ {
+ added: true,
+ lineNumber: 6,
+ line: " 'src/global.css',"
+ },
+ {
+ added: true,
+ lineNumber: 7,
+ line: " 'src/colors/index.ts',"
+ },
+ {
+ added: true,
+ lineNumber: 8,
+ line: " 'src/plugin.js'"
+ },
+ {
+ added: true,
+ lineNumber: 9,
+ line: ' ],'
+ }
+ ]
+ }
+ ]
+ })
+ })
+
+ test('Leading blank line is counted', () => {
+ const rawGitPatch = readFileSync(
+ path.join(__dirname, 'fixture.leading_blank_line.diff'),
+ 'utf8'
+ )
+ const parsedGitPatch = parseGitPatch(rawGitPatch)
+
+ assert.ok(parsedGitPatch.files)
+ assert.equal(parsedGitPatch.files.length, 1)
+ assert.deepEqual(parsedGitPatch.files[0], {
+ added: false,
+ deleted: false,
+ beforeName: 'src/App.tsx',
+ afterName: 'src/App.tsx',
+ modifiedLines: [
+ {
+ added: true,
+ lineNumber: 8,
+ line: " console.log('hello world')"
+ }
+ ]
+ })
+ })
+})