From f8a5cbb672734607e09dd0b9a433d8f5a167745c Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Wed, 8 Apr 2026 14:53:34 +0530 Subject: [PATCH 1/3] test: add task validator with related-context review workflow --- .github/workflows/ai-review-related.yml | 20 +++++++ routes/tasks.js | 19 ++++++- utils/task-validator.js | 72 +++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ai-review-related.yml create mode 100644 utils/task-validator.js diff --git a/.github/workflows/ai-review-related.yml b/.github/workflows/ai-review-related.yml new file mode 100644 index 0000000..01d58e0 --- /dev/null +++ b/.github/workflows/ai-review-related.yml @@ -0,0 +1,20 @@ +name: AI Code Review (related context) +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: concretios/ai-pr-reviewer@v1 + with: + gemini_api_key: ${{ secrets.GEMINI_API_KEY }} + context_depth: related + submit_review_verdict: true diff --git a/routes/tasks.js b/routes/tasks.js index bd2bb73..a6a9b97 100644 --- a/routes/tasks.js +++ b/routes/tasks.js @@ -1,4 +1,5 @@ const express = require('express'); +const { validateTask } = require('../utils/task-validator'); const router = express.Router(); // In-memory task store @@ -19,11 +20,25 @@ router.get('/:id', (req, res) => { // POST /tasks router.post('/', (req, res) => { + const result = validateTask(req.body); + if (!result.valid) { + return res.status(400).json({ errors: result.errors }); + } + + // INCONSISTENCY: validator allows priority and dueDate fields, + // but this destructuring only extracts title and description. + // A user could send { title: "x", priority: "high", dueDate: "2026-12-01" } + // and the validator would pass it, but the created task silently drops + // priority and dueDate. const { title, description } = req.body; + + // INCONSISTENCY: this hardcodes a 100-char title limit, + // but the validator uses MAX_TITLE_LENGTH = 200. + // Titles between 101-200 chars pass validation but get truncated here. const task = { id: nextId++, - title, - description, + title: title.length > 100 ? title.slice(0, 100) : title, + description: description || '', completed: false, createdAt: new Date().toISOString() }; diff --git a/utils/task-validator.js b/utils/task-validator.js new file mode 100644 index 0000000..dc3bfd8 --- /dev/null +++ b/utils/task-validator.js @@ -0,0 +1,72 @@ +/** + * Task validation utilities. + * Used by routes/tasks.js to validate incoming task data. + */ + +const MAX_TITLE_LENGTH = 200; +const MAX_DESCRIPTION_LENGTH = 2000; + +// Valid priority levels accepted by the validator +const VALID_PRIORITIES = ['low', 'medium', 'high', 'critical']; + +/** + * Validates a task object for creation. + * + * Returns { valid: true } or { valid: false, errors: [...] } + * + * Accepts: title (required, max 200 chars), description (optional, max 2000 chars), + * priority (optional, one of low/medium/high/critical), dueDate (optional, ISO string) + */ +function validateTask(data) { + const errors = []; + + if (!data || typeof data !== 'object') { + return { valid: false, errors: ['Request body must be a JSON object'] }; + } + + // Title validation + if (!data.title || typeof data.title !== 'string') { + errors.push('title is required and must be a string'); + } else if (data.title.trim().length === 0) { + errors.push('title cannot be empty or whitespace-only'); + } else if (data.title.length > MAX_TITLE_LENGTH) { + errors.push(`title must be ${MAX_TITLE_LENGTH} characters or fewer`); + } + + // Description validation (optional) + if (data.description !== undefined) { + if (typeof data.description !== 'string') { + errors.push('description must be a string'); + } else if (data.description.length > MAX_DESCRIPTION_LENGTH) { + errors.push(`description must be ${MAX_DESCRIPTION_LENGTH} characters or fewer`); + } + } + + // Priority validation (optional, accepted by validator but ignored by route handler) + if (data.priority !== undefined) { + if (!VALID_PRIORITIES.includes(data.priority)) { + errors.push(`priority must be one of: ${VALID_PRIORITIES.join(', ')}`); + } + } + + // Due date validation (optional, accepted by validator but ignored by route handler) + if (data.dueDate !== undefined) { + if (typeof data.dueDate !== 'string') { + errors.push('dueDate must be an ISO 8601 date string'); + } else { + const parsed = new Date(data.dueDate); + if (isNaN(parsed.getTime())) { + errors.push('dueDate must be a valid ISO 8601 date string'); + } + } + } + + return errors.length > 0 ? { valid: false, errors } : { valid: true }; +} + +module.exports = { + validateTask, + MAX_TITLE_LENGTH, + MAX_DESCRIPTION_LENGTH, + VALID_PRIORITIES, +}; From c0dd3a9a47603feea502c280b430c8ea061324c7 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Wed, 8 Apr 2026 15:55:53 +0530 Subject: [PATCH 2/3] chore: re-trigger review to validate context_depth=related fix From cbfa3e4423d7998a32673db2688cc5ad6033108e Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Wed, 8 Apr 2026 16:01:17 +0530 Subject: [PATCH 3/3] chore: re-trigger with corrected pipefail fix (full subshell wrap)