Skip to content

Commit

Permalink
Merge pull request #13 from braintree/validate-months-based-on-year
Browse files Browse the repository at this point in the history
Contextually validate expiration months based on current year.
  • Loading branch information
kyledetella committed Jun 23, 2015
2 parents 418c646 + e445324 commit 07deb8f
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 113 deletions.
13 changes: 10 additions & 3 deletions src/expiration-date.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand Down
11 changes: 8 additions & 3 deletions src/expiration-month.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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;
14 changes: 10 additions & 4 deletions src/expiration-year.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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;
87 changes: 68 additions & 19 deletions test/unit/expiration-date.js
Original file line number Diff line number Diff line change
@@ -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}],
Expand All @@ -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': [
Expand Down
116 changes: 84 additions & 32 deletions test/unit/expiration-month.js
Original file line number Diff line number Diff line change
@@ -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
}
]
]
};

Expand Down
Loading

0 comments on commit 07deb8f

Please sign in to comment.