diff --git a/controllers/puzzle-strings.js b/controllers/puzzle-strings.js index a5b0737..7a67cb8 100644 --- a/controllers/puzzle-strings.js +++ b/controllers/puzzle-strings.js @@ -18,5 +18,9 @@ export const puzzlesAndSolutions = [ [ '82..4..6...16..89...98315.749.157.............53..4...96.415..81..7632..3...28.51', '827549163531672894649831527496157382218396475753284916962415738185763249374928651' + ], + [ + // unsolvable + '9.9..5.1.85.4....2432......1...69.83.9.....6.62.71...9......1945....4.37.4.3..6..' ] ]; \ No newline at end of file diff --git a/controllers/sudoku-solver.js b/controllers/sudoku-solver.js index 16e5dc8..18aa060 100644 --- a/controllers/sudoku-solver.js +++ b/controllers/sudoku-solver.js @@ -1,5 +1,3 @@ -import { columns } from '../models/columns'; -import { rows } from '../models/rows'; import { grids } from '../models/grids'; class SudokuSolver { @@ -16,6 +14,18 @@ class SudokuSolver { checkRowPlacement(puzzleString, row, column, value) { + const rows = { + A: [], + B: [], + C: [], + D: [], + E: [], + F: [], + G: [], + H: [], + I: [], + }; + // rowKeys const rowKeys = Object.keys(rows); @@ -45,8 +55,20 @@ class SudokuSolver { checkColPlacement(puzzleString, row, column, value) { - const columnKeys = Object.keys(columns); + const columns = { + 1: [], + 2: [], + 3: [], + 4: [], + 5: [], + 6: [], + 7: [], + 8: [], + 9: [], + }; + const columnKeys = Object.keys(columns); + columnKeys.forEach((key, i) => { let values = [] @@ -58,12 +80,9 @@ class SudokuSolver { const columnValue = puzzleString.slice(start, end); - values.push(columnValue); - + columns[n].push(columnValue); }); - columns[key] = values; - }); // get column values for placement @@ -103,7 +122,7 @@ class SudokuSolver { default: break; } - + // make sure placement location doesn't contain a value and column doesn't contain value // or location already contains value return (existingValue == '.' && !columnValues.includes(value)) || existingValue == value; diff --git a/models/columns.js b/models/columns.js deleted file mode 100644 index ad28057..0000000 --- a/models/columns.js +++ /dev/null @@ -1,11 +0,0 @@ -export const columns = { - 1: [], - 2: [], - 3: [], - 4: [], - 5: [], - 6: [], - 7: [], - 8: [], - 9: [], -}; \ No newline at end of file diff --git a/models/rows.js b/models/rows.js deleted file mode 100644 index 834b7d3..0000000 --- a/models/rows.js +++ /dev/null @@ -1,11 +0,0 @@ -export const rows = { - A: [], - B: [], - C: [], - D: [], - E: [], - F: [], - G: [], - H: [], - I: [], -}; \ No newline at end of file diff --git a/routes/api.js b/routes/api.js index 74e4bf1..65fc2ee 100644 --- a/routes/api.js +++ b/routes/api.js @@ -1,8 +1,6 @@ 'use strict'; const SudokuSolver = require('../controllers/sudoku-solver'); -import { columns } from '../models/columns'; -import { rows } from '../models/rows'; module.exports = function (app) { @@ -31,8 +29,8 @@ module.exports = function (app) { const col = coordinate.slice(1, coordinate.length); // get the valid rows and cols - const validRows = Object.keys(rows); - const validColumns = Object.keys(columns); + const validRows = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']; + const validColumns = ['1', '2', '3', '4', '5', '6', '7', '8', '9']; // if the passed row or col value are not valid return error. if (!validRows.includes(row) || !validColumns.includes(col)) return res.status(200).json({error: 'Invalid coordinate'}); diff --git a/tests/2_functional-tests.js b/tests/2_functional-tests.js index 12f0893..e4910a2 100644 --- a/tests/2_functional-tests.js +++ b/tests/2_functional-tests.js @@ -2,9 +2,207 @@ const chai = require("chai"); const chaiHttp = require('chai-http'); const assert = chai.assert; const server = require('../server'); +const { puzzlesAndSolutions } = require('../controllers/puzzle-strings'); chai.use(chaiHttp); suite('Functional Tests', () => { + test('POST /api/solve Solve a puzzle with valid puzzle string', (done) => { + + const puzzleString = puzzlesAndSolutions[0][0]; + const solution = puzzlesAndSolutions[0][1]; + + chai.request(server) + .post('/api/solve') + .set('content-type', 'application/x-www-urlencoded') + .type('form') + .send(`puzzle=${puzzleString}`) + .end((err, res) => { + + if (err) console.log(err); + + assert.equal(res.status, 200); + assert.isObject(res.body); + assert.propertyVal(res.body, 'solution', solution); + + done(); + + }); + + }); + + test('POST /api/solve Solve a puzzle with missing puzzle string', (done) => { + + chai.request(server) + .post('/api/solve') + .set('content-type', 'application/x-www-urlencoded') + .type('form') + .end((err, res) => { + + if (err) console.log(err); + + assert.equal(res.status, 200); + assert.isObject(res.body); + assert.propertyVal(res.body, 'error', 'Required field missing'); + + done(); + + }); + + }); + + test('POST /api/solve Solve a puzzle with invalid characters', (done) => { + + const puzzleString = puzzlesAndSolutions[0][0].slice(0,80) + '?'; + + chai.request(server) + .post('/api/solve') + .set('content-type', 'application/x-www-urlencoded') + .type('form') + .send(`puzzle=${puzzleString}`) + .end((err, res) => { + + if (err) console.log(err); + + assert.equal(res.status, 200); + assert.isObject(res.body); + assert.propertyVal(res.body, 'error', 'Invalid characters in puzzle'); + + done(); + + }); + + }); + + test('POST /api/solve Solve a puzzle with incorrect length', (done) => { + + const puzzleString = puzzlesAndSolutions[0][0] + '.'; + + chai.request(server) + .post('/api/solve') + .set('content-type', 'application/x-www-urlencoded') + .type('form') + .send(`puzzle=${puzzleString}`) + .end((err, res) => { + + if (err) console.log(err); + + assert.equal(res.status, 200); + assert.isObject(res.body); + assert.propertyVal(res.body, 'error', 'Expected puzzle to be 81 characters long'); + + done(); + + }); + + }); + + test('POST /api/solve Solve a puzzle that cannot be solved', (done) => { + + const puzzleString = puzzlesAndSolutions[5][0] + + chai.request(server) + .post('/api/solve') + .set('content-type', 'application/x-www-urlencoded') + .type('form') + .send(`puzzle=${puzzleString}`) + .end((err, res) => { + + if (err) console.log(err); + + assert.equal(res.status, 200); + assert.isObject(res.body); + assert.propertyVal(res.body, 'error', 'Puzzle cannot be solved'); + + done(); + + }); + + }); + + test('POST /api/check Check a puzzle placement with all fields', (done) => { + + const puzzleString = puzzlesAndSolutions[0][0]; + const coordinate = 'A2'; + const value = '3'; + + chai.request(server) + .post('/api/check') + .set('content-type', 'application/x-www-urlencoded') + .type('form') + .send(`puzzle=${puzzleString}`) + .send(`coordinate=${coordinate}`) + .send(`value=${value}`) + .end((err, res) => { + + if (err) console.log(err); + + assert.equal(res.status, 200); + assert.isObject(res.body); + assert.propertyVal(res.body, 'valid', true); + + done(); + + }); + + + }); + + test('POST /api/check Check a puzzle placement with single placement conflict', (done) => { + + assert.fail(); + done(); + + }); + + test('POST /api/check Check a puzzle placement with multiple placement conflicts', (done) => { + + assert.fail(); + done(); + + }); + + test('POST /api/check Check a puzzle placement with all placement conflicts', (done) => { + + assert.fail(); + done(); + + }); + + test('POST /api/check Check a puzzle placement with missing required fields', (done) => { + + assert.fail(); + done(); + + }); + + test('POST /api/check Check a puzzle placement with invalid characters', (done) => { + + assert.fail(); + done(); + + }); + + test('POST /api/check Check a puzzle placement with incorrect length', (done) => { + + assert.fail(); + done(); + + }); + + test('POST /api/check Check a puzzle placement with invalid placement coordinate', (done) => { + + assert.fail(); + done(); + + }); + + test('POST /api/check Check a puzzle placement with invalid placement value', (done) => { + + assert.fail(); + done(); + + }); + }); \ No newline at end of file