From 4593b3016d42a6db66125c61c8f21f128b893c01 Mon Sep 17 00:00:00 2001 From: kyledetella Date: Mon, 22 Jun 2015 21:10:19 +0000 Subject: [PATCH] Contextually validate expiration months based on current year. --- src/expiration-date.js | 13 +++- src/expiration-month.js | 11 +++- src/expiration-year.js | 14 ++-- test/unit/expiration-date.js | 87 +++++++++++++++++++------ test/unit/expiration-month.js | 116 ++++++++++++++++++++++++---------- test/unit/expiration-year.js | 106 ++++++++++++++++--------------- 6 files changed, 234 insertions(+), 113 deletions(-) diff --git a/src/expiration-date.js b/src/expiration-date.js index 983ca48..d2c0601 100644 --- a/src/expiration-date.js +++ b/src/expiration-date.js @@ -13,7 +13,7 @@ function verification(isValid, isPotentiallyValid, month, year) { } function expirationDate(value) { - var date, monthValid, yearValid; + var date, monthValid, yearValid, isValidForThisYear; if (!isString(value)) { return verification(false, false, null, null); @@ -24,8 +24,15 @@ function expirationDate(value) { monthValid = expirationMonth(date.month); yearValid = expirationYear(date.year); - if (monthValid.isValid && yearValid.isValid) { - return verification(true, true, date.month, date.year); + if (yearValid.isValid) { + if (yearValid.isCurrentYear) { + isValidForThisYear = monthValid.isValidForThisYear; + return verification(isValidForThisYear, isValidForThisYear, date.month, date.year); + } + + if (monthValid.isValid) { + return verification(true, true, date.month, date.year); + } } if (monthValid.isPotentiallyValid && yearValid.isPotentiallyValid) { diff --git a/src/expiration-month.js b/src/expiration-month.js index af485a7..99f807c 100644 --- a/src/expiration-month.js +++ b/src/expiration-month.js @@ -1,11 +1,16 @@ var isString = require('lodash.isstring'); -function verification(isValid, isPotentiallyValid) { - return {isValid: isValid, isPotentiallyValid: isPotentiallyValid}; +function verification(isValid, isPotentiallyValid, isValidForThisYear) { + return { + isValid: isValid, + isPotentiallyValid: isPotentiallyValid, + isValidForThisYear: isValidForThisYear || false + }; } function expirationMonth(value) { var month, result; + var currentMonth = new Date().getMonth() + 1; if (!isString(value)) { return verification(false, false); @@ -25,7 +30,7 @@ function expirationMonth(value) { result = month > 0 && month < 13; - return verification(result, result); + return verification(result, result, result && month >= currentMonth); } module.exports = expirationMonth; diff --git a/src/expiration-year.js b/src/expiration-year.js index c098585..57baf10 100644 --- a/src/expiration-year.js +++ b/src/expiration-year.js @@ -1,12 +1,16 @@ var isString = require('lodash.isstring'); var maxYear = 19; -function verification(isValid, isPotentiallyValid) { - return {isValid: isValid, isPotentiallyValid: isPotentiallyValid}; +function verification(isValid, isPotentiallyValid, isCurrentYear) { + return { + isValid: isValid, + isPotentiallyValid: isPotentiallyValid, + isCurrentYear: isCurrentYear || false + }; } function expirationYear(value) { - var currentFirstTwo, currentYear, firstTwo, len, twoDigitYear, valid; + var currentFirstTwo, currentYear, firstTwo, len, twoDigitYear, valid, isCurrentYear; if (!isString(value)) { return verification(false, false); @@ -41,12 +45,14 @@ function expirationYear(value) { twoDigitYear = Number(String(currentYear).substr(2, 2)); if (len === 2) { + isCurrentYear = twoDigitYear === value; valid = value >= twoDigitYear && value <= twoDigitYear + maxYear; } else if (len === 4) { + isCurrentYear = currentYear === value; valid = value >= currentYear && value <= currentYear + maxYear; } - return verification(valid, valid); + return verification(valid, valid, isCurrentYear); } module.exports = expirationYear; diff --git a/test/unit/expiration-date.js b/test/unit/expiration-date.js index 241d61d..103b8b4 100644 --- a/test/unit/expiration-date.js +++ b/test/unit/expiration-date.js @@ -1,23 +1,72 @@ var expect = require('chai').expect; var expirationDate = require('../../src/expiration-date'); +var date = new Date(); +var currentYear = date.getFullYear(); +var twoDigitYear = Number(String(currentYear).substr(2, 2)); +var nextYear = currentYear + 1; +var currentMonth = date.getMonth() + 1; +var previousMonth = currentMonth - 1 || currentMonth; +var nextMonth = currentMonth === 12 ? currentMonth : currentMonth + 1; + describe('expirationDate validates', function () { var describes = { + 'within current year': [ + [ + previousMonth + ' / ' + currentYear, + { + isValid: false, + isPotentiallyValid: false, + month: previousMonth.toString(), + year: currentYear.toString() + } + ], + [ + previousMonth + ' / ' + nextYear, + { + isValid: true, + isPotentiallyValid: true, + month: previousMonth.toString(), + year: nextYear.toString() + } + ], + [ + currentMonth + ' / ' + currentYear, + { + isValid: true, + isPotentiallyValid: true, + month: currentMonth.toString(), + year: currentYear.toString() + } + ], + [ + nextMonth + ' / ' + currentYear, + { + isValid: true, + isPotentiallyValid: true, + month: nextMonth.toString(), + year: currentYear.toString() + } + ] + ], + 'valid expiration dates with slashes': [ - ['10 / 2016', {isValid: true, isPotentiallyValid: true, month: '10', year: '2016'}], - ['10/2016', {isValid: true, isPotentiallyValid: true, month: '10', year: '2016'}], - ['12 / 2016', {isValid: true, isPotentiallyValid: true, month: '12', year: '2016'}], - ['01 / 2016', {isValid: true, isPotentiallyValid: true, month: '01', year: '2016'}], - ['09 / 2016', {isValid: true, isPotentiallyValid: true, month: '09', year: '2016'}], - ['01 / 16', {isValid: true, isPotentiallyValid: true, month: '01', year: '16'}], - ['01 / 2016', {isValid: true, isPotentiallyValid: true, month: '01', year: '2016'}], - ['01 / 2016', {isValid: true, isPotentiallyValid: true, month: '01', year: '2016'}] + [previousMonth + ' / ' + currentYear, {isValid: false, isPotentiallyValid: false, month: previousMonth.toString(), year: currentYear.toString()}], + [currentMonth + ' / ' + currentYear, {isValid: true, isPotentiallyValid: true, month: currentMonth.toString(), year: currentYear.toString()}], + ['10 / ' + nextYear, {isValid: true, isPotentiallyValid: true, month: '10', year: nextYear.toString()}], + ['10/' + nextYear, {isValid: true, isPotentiallyValid: true, month: '10', year: nextYear.toString()}], + ['12 / ' + nextYear, {isValid: true, isPotentiallyValid: true, month: '12', year: nextYear.toString()}], + ['01 / ' + nextYear, {isValid: true, isPotentiallyValid: true, month: '01', year: nextYear.toString()}], + ['09 / ' + nextYear, {isValid: true, isPotentiallyValid: true, month: '09', year: nextYear.toString()}], + ['01 / ' + (twoDigitYear + 1), {isValid: true, isPotentiallyValid: true, month: '01', year: (twoDigitYear + 1).toString()}], + ['01 / ' + nextYear, {isValid: true, isPotentiallyValid: true, month: '01', year: nextYear.toString()}], + ['01 / ' + nextYear, {isValid: true, isPotentiallyValid: true, month: '01', year: nextYear.toString()}] ], 'invalid expiration dates with slashes': [ ['11 / 11', {isValid: false, isPotentiallyValid: false, month: null, year: null}], - ['00 / 2016', {isValid: false, isPotentiallyValid: false, month: null, year: null}], - ['13 / 2016', {isValid: false, isPotentiallyValid: false, month: null, year: null}], + ['00 / ' + nextYear, {isValid: false, isPotentiallyValid: false, month: null, year: null}], + ['13 / ' + nextYear, {isValid: false, isPotentiallyValid: false, month: null, year: null}], ['01 / 1999', {isValid: false, isPotentiallyValid: false, month: null, year: null}], ['01/1999', {isValid: false, isPotentiallyValid: false, month: null, year: null}], ['01 / 2100', {isValid: false, isPotentiallyValid: false, month: null, year: null}], @@ -33,20 +82,20 @@ describe('expirationDate validates', function () { ], 'valid expiration dates with no slashes': [ - ['102016', {isValid: true, isPotentiallyValid: true, month: '10', year: '2016'}], - ['102016', {isValid: true, isPotentiallyValid: true, month: '10', year: '2016'}], - ['122016', {isValid: true, isPotentiallyValid: true, month: '12', year: '2016'}], - ['012016', {isValid: true, isPotentiallyValid: true, month: '01', year: '2016'}], - ['092016', {isValid: true, isPotentiallyValid: true, month: '09', year: '2016'}], + ['10' + nextYear, {isValid: true, isPotentiallyValid: true, month: '10', year: nextYear.toString()}], + ['10' + nextYear, {isValid: true, isPotentiallyValid: true, month: '10', year: nextYear.toString()}], + ['12' + nextYear, {isValid: true, isPotentiallyValid: true, month: '12', year: nextYear.toString()}], + ['01' + nextYear, {isValid: true, isPotentiallyValid: true, month: '01', year: nextYear.toString()}], + ['09' + nextYear, {isValid: true, isPotentiallyValid: true, month: '09', year: nextYear.toString()}], ['1219', {isValid: true, isPotentiallyValid: true, month: '12', year: '19'}], ['0116', {isValid: true, isPotentiallyValid: true, month: '01', year: '16'}] ], 'valid space separated month and year': [ - ['01 2019', {isValid: true, isPotentiallyValid: true, month: '01', year: '2019'}], - ['01 2020', {isValid: true, isPotentiallyValid: true, month: '01', year: '2020'}], - ['01 19', {isValid: true, isPotentiallyValid: true, month: '01', year: '19'}], - ['01 21', {isValid: true, isPotentiallyValid: true, month: '01', year: '21'}] + ['01 ' + (currentYear + 4), {isValid: true, isPotentiallyValid: true, month: '01', year: (currentYear + 4).toString()}], + ['01 ' + (currentYear + 5), {isValid: true, isPotentiallyValid: true, month: '01', year: (currentYear + 5).toString()}], + ['01 ' + (twoDigitYear + 4), {isValid: true, isPotentiallyValid: true, month: '01', year: (twoDigitYear + 4).toString()}], + ['01 ' + (twoDigitYear + 6), {isValid: true, isPotentiallyValid: true, month: '01', year: (twoDigitYear + 6).toString()}] ], 'invalid expiration dates with no slashes': [ diff --git a/test/unit/expiration-month.js b/test/unit/expiration-month.js index a14c7e1..3cf41ba 100644 --- a/test/unit/expiration-month.js +++ b/test/unit/expiration-month.js @@ -1,53 +1,105 @@ var expect = require('chai').expect; var expirationMonth = require('../../src/expiration-month'); +var currentMonth = new Date().getMonth() + 1; +var previousMonth = currentMonth - 1 || 1; +var nextMonth = currentMonth < 12 ? currentMonth + 1 : currentMonth; + describe('expirationMonth', function () { + var FALSE_VALIDATION = {isValid: false, isPotentiallyValid: false, isValidForThisYear: false}; + var TRUE_VALIDATION = {isValid: true, isPotentiallyValid: true, isValidForThisYear: true}; + var describes = { 'returns false if not a string': [ - [[], {isValid: false, isPotentiallyValid: false}], - [{}, {isValid: false, isPotentiallyValid: false}], - [null, {isValid: false, isPotentiallyValid: false}], - [undefined, {isValid: false, isPotentiallyValid: false}], - [Infinity, {isValid: false, isPotentiallyValid: false}], - [0 / 0, {isValid: false, isPotentiallyValid: false}], - [0, {isValid: false, isPotentiallyValid: false}], - [1, {isValid: false, isPotentiallyValid: false}], - [2, {isValid: false, isPotentiallyValid: false}], - [12, {isValid: false, isPotentiallyValid: false}], - [13, {isValid: false, isPotentiallyValid: false}], - [-1, {isValid: false, isPotentiallyValid: false}], - [-12, {isValid: false, isPotentiallyValid: false}] + [[], FALSE_VALIDATION], + [{}, FALSE_VALIDATION], + [null, FALSE_VALIDATION], + [undefined, FALSE_VALIDATION], + [Infinity, FALSE_VALIDATION], + [0 / 0, FALSE_VALIDATION], + [0, FALSE_VALIDATION], + [1, FALSE_VALIDATION], + [2, FALSE_VALIDATION], + [12, FALSE_VALIDATION], + [13, FALSE_VALIDATION], + [-1, FALSE_VALIDATION], + [-12, FALSE_VALIDATION] ], 'returns false for malformed strings': [ - ['foo', {isValid: false, isPotentiallyValid: false}], - ['1.2', {isValid: false, isPotentiallyValid: false}], - ['1/20', {isValid: false, isPotentiallyValid: false}], - ['1 2', {isValid: false, isPotentiallyValid: false}], - ['1 ', {isValid: false, isPotentiallyValid: false}], - [' 1', {isValid: false, isPotentiallyValid: false}] + ['foo', FALSE_VALIDATION], + ['1.2', FALSE_VALIDATION], + ['1/20', FALSE_VALIDATION], + ['1 2', FALSE_VALIDATION], + ['1 ', FALSE_VALIDATION], + [' 1', FALSE_VALIDATION] ], 'returns null for incomplete input': [ - ['', {isValid: false, isPotentiallyValid: true}], - ['0', {isValid: false, isPotentiallyValid: true}] + ['', {isValid: false, isPotentiallyValid: true, isValidForThisYear: false}], + ['0', {isValid: false, isPotentiallyValid: true, isValidForThisYear: false}] ], 'valid month': [ - ['1', {isValid: true, isPotentiallyValid: true}], - ['2', {isValid: true, isPotentiallyValid: true}], - ['5', {isValid: true, isPotentiallyValid: true}], - ['02', {isValid: true, isPotentiallyValid: true}], - ['12', {isValid: true, isPotentiallyValid: true}] + [currentMonth.toString(), TRUE_VALIDATION], + [nextMonth.toString(), TRUE_VALIDATION], + [ + '1', + { + isValid: true, + isPotentiallyValid: true, + isValidForThisYear: currentMonth <= 1 + } + ], + [ + '2', + { + isValid: true, + isPotentiallyValid: true, + isValidForThisYear: currentMonth <= 2 + } + ], + [ + '5', + { + isValid: true, + isPotentiallyValid: true, + isValidForThisYear: currentMonth <= 5 + } + ], + [ + '02', + { + isValid: true, + isPotentiallyValid: true, + isValidForThisYear: currentMonth <= 2 + } + ], + [ + '12', + { + isValid: true, + isPotentiallyValid: true, + isValidForThisYear: currentMonth <= 12 + } + ] ], 'invalid month': [ - ['14', {isValid: false, isPotentiallyValid: false}], - ['30', {isValid: false, isPotentiallyValid: false}], - ['-6', {isValid: false, isPotentiallyValid: false}], - ['20', {isValid: false, isPotentiallyValid: false}], - ['-1', {isValid: false, isPotentiallyValid: false}], - ['13', {isValid: false, isPotentiallyValid: false}] + ['14', FALSE_VALIDATION], + ['30', FALSE_VALIDATION], + ['-6', FALSE_VALIDATION], + ['20', FALSE_VALIDATION], + ['-1', FALSE_VALIDATION], + ['13', FALSE_VALIDATION], + [ + previousMonth.toString(), + { + isValid: true, + isPotentiallyValid: true, + isValidForThisYear: currentMonth <= 2 + } + ] ] }; diff --git a/test/unit/expiration-year.js b/test/unit/expiration-year.js index 42d37dc..64c64ba 100644 --- a/test/unit/expiration-year.js +++ b/test/unit/expiration-year.js @@ -11,76 +11,78 @@ function yearsFromNow(fromNow, digits) { } describe('expirationYear', function () { + var FALSE_VALIDATION = {isValid: false, isPotentiallyValid: false, isCurrentYear: false}; + var describes = { 'returns false if not a string': [ - [[], {isValid: false, isPotentiallyValid: false}], - [{}, {isValid: false, isPotentiallyValid: false}], - [null, {isValid: false, isPotentiallyValid: false}], - [undefined, {isValid: false, isPotentiallyValid: false}], - [Infinity, {isValid: false, isPotentiallyValid: false}], - [0 / 0, {isValid: false, isPotentiallyValid: false}], - [0, {isValid: false, isPotentiallyValid: false}], - [1, {isValid: false, isPotentiallyValid: false}], - [2, {isValid: false, isPotentiallyValid: false}], - [12, {isValid: false, isPotentiallyValid: false}], - [-1, {isValid: false, isPotentiallyValid: false}], - [-12, {isValid: false, isPotentiallyValid: false}] + [[], FALSE_VALIDATION], + [{}, FALSE_VALIDATION], + [null, FALSE_VALIDATION], + [undefined, FALSE_VALIDATION], + [Infinity, FALSE_VALIDATION], + [0 / 0, FALSE_VALIDATION], + [0, FALSE_VALIDATION], + [1, FALSE_VALIDATION], + [2, FALSE_VALIDATION], + [12, FALSE_VALIDATION], + [-1, FALSE_VALIDATION], + [-12, FALSE_VALIDATION] ], 'returns false for malformed strings': [ - ['foo', {isValid: false, isPotentiallyValid: false}], - ['1.2', {isValid: false, isPotentiallyValid: false}], - ['1/20', {isValid: false, isPotentiallyValid: false}], - ['1 2', {isValid: false, isPotentiallyValid: false}], - ['1 ', {isValid: false, isPotentiallyValid: false}], - [' 1', {isValid: false, isPotentiallyValid: false}], - ['20015', {isValid: false, isPotentiallyValid: false}] + ['foo', FALSE_VALIDATION], + ['1.2', FALSE_VALIDATION], + ['1/20', FALSE_VALIDATION], + ['1 2', FALSE_VALIDATION], + ['1 ', FALSE_VALIDATION], + [' 1', FALSE_VALIDATION], + ['20015', FALSE_VALIDATION] ], 'returns null for incomplete strings': [ - ['', {isValid: false, isPotentiallyValid: true}], - ['2', {isValid: false, isPotentiallyValid: true}], - ['9', {isValid: false, isPotentiallyValid: true}], - ['200', {isValid: false, isPotentiallyValid: true}], - ['123', {isValid: false, isPotentiallyValid: false}] + ['', {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + ['2', {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + ['9', {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + ['200', {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + ['123', {isValid: false, isPotentiallyValid: false, isCurrentYear: false}] ], 'accepts four-digit years': [ - [yearsFromNow(0), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(-5), {isValid: false, isPotentiallyValid: false}], - [yearsFromNow(5), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(10), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(11), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(12), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(19), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(20), {isValid: false, isPotentiallyValid: false}], - [yearsFromNow(25), {isValid: false, isPotentiallyValid: false}], - [yearsFromNow(33), {isValid: false, isPotentiallyValid: false}] + [yearsFromNow(0), {isValid: true, isPotentiallyValid: true, isCurrentYear: true}], + [yearsFromNow(-5), {isValid: false, isPotentiallyValid: false, isCurrentYear: false}], + [yearsFromNow(5), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(10), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(11), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(12), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(19), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(20), {isValid: false, isPotentiallyValid: false, isCurrentYear: false}], + [yearsFromNow(25), {isValid: false, isPotentiallyValid: false, isCurrentYear: false}], + [yearsFromNow(33), {isValid: false, isPotentiallyValid: false, isCurrentYear: false}] ], 'accepts two-digit years': [ - [yearsFromNow(0, 2), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(-5, 2), {isValid: false, isPotentiallyValid: false}], - [yearsFromNow(5, 2), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(10, 2), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(11, 2), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(12, 2), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(19, 2), {isValid: true, isPotentiallyValid: true}], - [yearsFromNow(20, 2), {isValid: false, isPotentiallyValid: false}], - [yearsFromNow(25, 2), {isValid: false, isPotentiallyValid: false}], - [yearsFromNow(33, 2), {isValid: false, isPotentiallyValid: false}] + [yearsFromNow(0, 2), {isValid: true, isPotentiallyValid: true, isCurrentYear: true}], + [yearsFromNow(-5, 2), {isValid: false, isPotentiallyValid: false, isCurrentYear: false}], + [yearsFromNow(5, 2), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(10, 2), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(11, 2), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(12, 2), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(19, 2), {isValid: true, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(20, 2), {isValid: false, isPotentiallyValid: false, isCurrentYear: false}], + [yearsFromNow(25, 2), {isValid: false, isPotentiallyValid: false, isCurrentYear: false}], + [yearsFromNow(33, 2), {isValid: false, isPotentiallyValid: false, isCurrentYear: false}] ], // This doesn't take 20xx -> 21xx into account, but probably YAGNI 'accepts three-digit years': [ - [yearsFromNow(-3).slice(0, 3), {isValid: false, isPotentiallyValid: true}], - [yearsFromNow(-1).slice(0, 3), {isValid: false, isPotentiallyValid: true}], - [yearsFromNow(0).slice(0, 3), {isValid: false, isPotentiallyValid: true}], - [yearsFromNow(1).slice(0, 3), {isValid: false, isPotentiallyValid: true}], - [yearsFromNow(5).slice(0, 3), {isValid: false, isPotentiallyValid: true}], - [yearsFromNow(11).slice(0, 3), {isValid: false, isPotentiallyValid: true}], - [yearsFromNow(17).slice(0, 3), {isValid: false, isPotentiallyValid: true}], - [yearsFromNow(23).slice(0, 3), {isValid: false, isPotentiallyValid: true}] + [yearsFromNow(-3).slice(0, 3), {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(-1).slice(0, 3), {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(0).slice(0, 3), {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(1).slice(0, 3), {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(5).slice(0, 3), {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(11).slice(0, 3), {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(17).slice(0, 3), {isValid: false, isPotentiallyValid: true, isCurrentYear: false}], + [yearsFromNow(23).slice(0, 3), {isValid: false, isPotentiallyValid: true, isCurrentYear: false}] ] };