/*
* Copyright 2018 Comcast Cable Communications Management, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
describe('BatchChange', function(){
beforeEach(function () {
module('dns-change'),
module('service.utility'),
module('service.paging'),
module('service.groups')
});
var deferred;
describe('Controller: DnsChangeDetailController', function(){
beforeEach(function () {
module('ngMock')
});
beforeEach(inject(function ($rootScope, $controller, $q, dnsChangeService, pagingService, utilityService) {
this.rootScope = $rootScope;
this.scope = $rootScope.$new();
this.controller = $controller('DnsChangeDetailController', {'$scope': this.scope});
deferred = $q.defer();
spyOn(dnsChangeService, 'getBatchChange').and.returnValue(deferred.promise);
spyOn(dnsChangeService, 'approveBatchChange').and.returnValue(deferred.promise);
spyOn(dnsChangeService, 'rejectBatchChange').and.returnValue(deferred.promise);
}));
describe('$scope.getBatchChange', function() {
it('should resolve the promise', inject(function(dnsChangeService) {
this.scope.getBatchChange("17350028-b2b8-428d-9f10-dbb518a0364d");
expect(dnsChangeService.getBatchChange).toHaveBeenCalled();
expect(dnsChangeService.getBatchChange).toHaveBeenCalledWith("17350028-b2b8-428d-9f10-dbb518a0364d");
var mockBatchChange = {
userId: "17350028-b2b8-428d-9f10-dbb518a0364d",
userName: "bwrigh833",
comments: "this is a test comment.",
createdTimestamp: "2018-05-09T14:20:50Z",
changes: [{zoneId: "937191c4-b1fd-4ab5-abb4-9553a65b44ab", zoneName: "old-vinyldns2.", recordName:"test1", inputName: "test1.old-vinyldns2.", type: "A", ttl: 200, record: {address:"1.1.1.1"}, status: "Pending", id: "c29d33e4-9bee-4417-a99b-6e815fdeb748", changeType: "Add"}],
status: "Pending",
id: "921e70fa-9bec-48eb-a520-a8e6106158e2"
};
deferred.resolve({data: mockBatchChange});
this.rootScope.$apply();
expect(this.scope.batch).toEqual(mockBatchChange);
}));
it('should reject the promise', inject(function(dnsChangeService) {
this.scope.getBatchChange("nonExistentBatchChange");
expect(dnsChangeService.getBatchChange).toHaveBeenCalled();
expect(dnsChangeService.getBatchChange).toHaveBeenCalledWith("nonExistentBatchChange");
deferred.reject({data: "Batch change with id wow cannot be found", status: 404});
this.rootScope.$apply();
expect(this.scope.batch).toEqual({});
expect(this.scope.alerts).toEqual([{ type: 'danger', content: 'HTTP 404 (undefined): Batch change with id wow cannot be found' }]);
}));
});
describe('$scope.confirmApprove', function() {
it('should resolve the promise', inject(function(dnsChangeService) {
this.scope.confirmApprove("17350028-b2b8-428d-9f10-dbb518a0364d", "great");
expect(dnsChangeService.approveBatchChange).toHaveBeenCalled();
deferred.resolve({data: {reviewComment: "great"}});
this.rootScope.$apply();
expect(this.scope.reviewConfirmationMsg).toEqual(null);
expect(this.scope.reviewType).toEqual(null);
}));
it('should reject the promise', inject(function(dnsChangeService) {
this.scope.confirmApprove("notPendingBatchChange", "great");
expect(dnsChangeService.approveBatchChange).toHaveBeenCalled();
var errorData = {data: [{changeType: "Add", inputName: "onofe.ok.", type: "A", ttl: 7200, record: {address: "1.1.1.1"}},
{changeType: "Add", inputName: "wfonef.wfoefn.", type: "A", ttl: 7200, record: {address: "1.1.1.1"}, errors: ['Zone Discovery Failed: zone for "wfonef.wfoefn." doesn\'t exists']}]}
deferred.reject({data: errorData, status: 400});
this.rootScope.$apply();
expect(this.scope.alerts).toEqual([{ type: 'danger', content: "HTTP 400 (undefined): Issues still remain, cannot approve DNS Change. Resolve all outstanding issues or reject the DNS Change." }]);
}));
});
describe('$scope.confirmReject', function() {
it('should resolve the promise', inject(function(dnsChangeService) {
this.scope.confirmReject("17350028-b2b8-428d-9f10-dbb518a0364d", "great");
expect(dnsChangeService.rejectBatchChange).toHaveBeenCalled();
deferred.resolve({data: {reviewComment: "no bueno"}});
this.rootScope.$apply();
expect(this.scope.reviewConfirmationMsg).toEqual(null);
expect(this.scope.reviewType).toEqual(null);
}));
it('should reject the promise', inject(function(dnsChangeService) {
this.scope.confirmReject("notPendingBatchChange", "no bueno");
expect(dnsChangeService.rejectBatchChange).toHaveBeenCalled();
deferred.reject({data: "Batch change with id notPendingBatchChange is not pending review.", status: 400});
this.rootScope.$apply();
expect(this.scope.alerts).toEqual([{ type: 'danger', content: 'HTTP 400 (undefined): Batch change with id notPendingBatchChange is not pending review.' }]);
}));
});
});
describe('Controller: DnsChangeNewController', function(){
var tomorrow = moment().startOf('hour').add(1, 'day').format('LL hh:mm A');
beforeEach(function () {
module('ngMock')
});
beforeEach(inject(function ($rootScope, $controller, $q, dnsChangeService, utilityService, groupsService) {
this.rootScope = $rootScope;
this.scope = $rootScope.$new();
this.groupsService = groupsService;
this.scope.newBatch = {comments: "", changes: [{changeType: "Add", type: "A", ttl: 200}]};
this.scope.myGroups = {};
deferred = $q.defer();
spyOn(dnsChangeService, 'createBatchChange').and.returnValue(deferred.promise);
groupsService.getGroups = function() {
return $q.when({
data: {
groups: "all my groups"
}
});
};
this.controller = $controller('DnsChangeNewController', {'$scope': this.scope});
}));
it("test that we properly get user's groups when loading DnsChangeNewController", function(){
this.scope.$digest();
expect(this.scope.myGroups).toBe("all my groups");
});
it("test that we set a default scheduledTime when loading DnsChangeNewController", function(){
this.scope.$digest();
expect(this.scope.newBatch.scheduledTime).toBe(tomorrow);
});
describe('$scope.addSingleChange', function() {
it('adds a change to the changes array', function() {
this.scope.addSingleChange();
expect(this.scope.newBatch).toEqual({comments: "", changes: [{changeType: "Add", type: "A+PTR"}, {changeType: "Add", type: "A+PTR"}], scheduledTime: tomorrow})
});
});
describe('$scope.deleteSingleChange', function() {
it('removes a change from the changes array', function() {
this.scope.deleteSingleChange(0);
expect(this.scope.newBatch).toEqual({comments: "", changes: [], scheduledTime: tomorrow})
});
});
describe('$scope.uploadCSV', function() {
beforeEach(inject(function($compile) {
var formInput = $(''+
'' +
'');
$(document.body).append(formInput);
$compile(formInput)(this.scope);
this.scope.$digest();
}));
it('parses a CSV file', function(done) {
var fileBlob = new Blob(["Change Type,Record Type,Input Name,TTL,Record Data\nAdd,A+PTR,test.example.,200,1.1.1.1"], { type: 'text/csv' });
var batchChange = this.scope.newBatch;
this.scope.uploadCSV(fileBlob);
setTimeout(function() {
expect(batchChange.changes.length).toEqual(1);
expect(batchChange).toEqual({comments: "", changes: [{changeType: "Add", type: "A+PTR", inputName: "test.example.", ttl: 200, record: {address: "1.1.1.1"}}], scheduledTime: tomorrow});
done();
}, 2000);
})
it('is case insensitive', function(done) {
var fileBlob = new Blob(["Change Type,Record Type,Input Name,TTL,Record Data\nadd,a+pTR,test.example.,200,1.1.1.1"], { type: 'text/csv' });
var batchChange = this.scope.newBatch;
this.scope.uploadCSV(fileBlob);
setTimeout(function() {
expect(batchChange.changes.length).toEqual(1)
expect(batchChange).toEqual({comments: "", changes: [{changeType: "Add", type: "A+PTR", inputName: "test.example.", ttl: 200, record: {address: "1.1.1.1"}}], scheduledTime: tomorrow});
done();
}, 1000);
})
it('handles "delete" change type', function(done) {
var fileBlob = new Blob(["Change Type,Record Type,Input Name,TTL,Record Data\nDelete,A+PTR,test.example.,200,1.1.1.1"], { type: 'text/csv' });
var batchChange = this.scope.newBatch;
this.scope.uploadCSV(fileBlob);
setTimeout(function() {
expect(batchChange.changes.length).toEqual(1)
expect(batchChange).toEqual({comments: "", changes: [{changeType: "DeleteRecordSet", type: "A+PTR", inputName: "test.example.", ttl: 200, record: {address: "1.1.1.1"}}], scheduledTime: tomorrow});
done();
}, 1000);
})
it('handles whitespace', function(done) {
var fileBlob = new Blob(["Change Type,Record Type,Input Name,TTL,Record Data\nDelete, A+PTR ,test.example. ,200,1.1.1.1"], { type: 'text/csv' });
var batchChange = this.scope.newBatch;
this.scope.uploadCSV(fileBlob);
setTimeout(function() {
expect(batchChange.changes.length).toEqual(1)
expect(batchChange).toEqual({comments: "", changes: [{changeType: "DeleteRecordSet", type: "A+PTR", inputName: "test.example.", ttl: 200, record: {address: "1.1.1.1"}}], scheduledTime: tomorrow});
done();
}, 1000);
})
it('fails if the first line is not the header row', function(done) {
var fileBlob = new Blob(["Delete,A+PTR,test.example.,,1.1.1.1\nAdd,A+PTR,test.add.,200,1.1.1.1"], { type: 'text/csv' });
var batchChange = this.scope.newBatch;
this.scope.uploadCSV(fileBlob);
var alerts = this.scope.alerts;
setTimeout(function() {
expect(batchChange.changes.length).toEqual(1)
expect(batchChange).toEqual({comments: "", changes: [{changeType: "Add", type: "A+PTR"}], scheduledTime: tomorrow});
done();
}, 1000);
})
it('does not include empty lines', function(done) {
var fileBlob = new Blob(["Change Type,Record Type,Input Name,TTL,Record Data\n,,,,,\nDelete,A+PTR,test.example.,200,1.1.1.1"], { type: 'text/csv' });
var batchChange = this.scope.newBatch;
this.scope.uploadCSV(fileBlob);
setTimeout(function() {
expect(batchChange.changes.length).toEqual(1)
expect(batchChange).toEqual({comments: "", changes: [{changeType: "DeleteRecordSet", type: "A+PTR", inputName: "test.example.", ttl: 200, record: {address: "1.1.1.1"}}], scheduledTime: tomorrow});
done();
}, 1000);
})
it('does not import non-CSV format files', function() {
var newFile = new Blob([], {type: 'any'});
this.scope.uploadCSV(newFile);
expect(this.scope.newBatch).toEqual({comments: "", changes: [{changeType: "Add", type: "A+PTR"}], scheduledTime: tomorrow});
});
});
describe('$scope.createBatchChange', function() {
it('should resolve the promise', inject(function(dnsChangeService) {
this.scope.newBatch = {
comments: "this is a comment.",
changes: [{changeType: "Add", type: "A", ttl: 200, record: {address: "1.1.1.2"}}]
};
this.scope.createBatchChange();
expect(dnsChangeService.createBatchChange).toHaveBeenCalled();
var mockBatchChange = {
userId: "17350028-b2b8-428d-9f10-dbb518a0364d",
userName: "bwrigh833",
comments: "this is a test comment.",
createdTimestamp: "2018-05-09T14:20:50Z",
changes: [{zoneId: "937191c4-b1fd-4ab5-abb4-9553a65b44ab", zoneName: "old-vinyldns2.", recordName:"test1", inputName: "test1.old-vinyldns2.", type: "A", ttl: 200, record: {address:"1.1.1.1"}, status: "Pending", id: "c29d33e4-9bee-4417-a99b-6e815fdeb748", changeType: "Add"}],
status: "Pending",
id: "921e70fa-9bec-48eb-a520-a8e6106158e2"
};
deferred.resolve({data: mockBatchChange});
this.rootScope.$apply();
expect(this.scope.batch).toEqual(mockBatchChange);
}));
it('should reject the promise', inject(function(batchChangeService) {
this.scope.newBatch = {
comments: "zone not found.",
changes: [{changeType: "Add", inputName: 'blah.dummy.', type: "A", ttl: 200, record: {address: "1.1.1.2"}}]
};
this.scope.createBatchChange();
expect(dnsChangeService.createBatchChange).toHaveBeenCalled();
deferred.reject({config: {data: this.scope.newBatch}, data: [{changeType: "Add", inputName: 'blah.dummy.', type: "A", ttl: 200, record: {address: "1.1.1.2"}, errors: ['Zone for "blah.dummy." does not exist in Vinyl.']}], status: 400});
this.rootScope.$apply();
expect(this.scope.batch).toEqual({});
expect(this.scope.newBatch).toEqual({
comments: "zone not found.",
changes: [{changeType: "Add", inputName: 'blah.dummy.', type: "A", ttl: 200, record: {address: "1.1.1.2"}, errors: ['Zone for "blah.dummy." does not exist in Vinyl.']}]
});
expect(this.scope.alerts).toEqual([{ type: 'danger', content: 'Errors found. Please correct and submit again.'}]);
}));
it('should format the batch change data', inject(function(dnsChangeService) {
this.scope.newBatch = {
comments: "this is a comment.",
changes: [{changeType: "Add", inputName: 'blah.dummy.', type: "A+PTR", ttl: 200, record: {address: "1.1.1.2"}}],
scheduledTime: tomorrow,
};
this.scope.createBatchChange();
expect(dnsChangeService.createBatchChange).toHaveBeenCalled();
deferred.resolve({data: {}});
this.rootScope.$apply();
expect(this.scope.newBatch.ownerGroupId).toBeUndefined();
expect(this.scope.newBatch.scheduledTime).toBeUndefined();
expect(this.scope.newBatch.changes).toEqual([
{changeType: "Add", inputName: 'blah.dummy.', type: "A", ttl: 200, record: {address: "1.1.1.2"}},
{changeType: "Add", inputName: '1.1.1.2', type: "PTR", ttl: 200, record: {ptrdname: "blah.dummy."}}
]);
}));
});
});
describe('Controller: DnsChangesController', function(){
beforeEach(function () {
module('ngMock')
});
beforeEach(inject(function ($rootScope, $controller, $q, dnsChangeService, utilityService) {
this.rootScope = $rootScope;
this.scope = $rootScope.$new();
this.controller = $controller('DnsChangesController', {'$scope': this.scope});
deferred = $q.defer();
spyOn(dnsChangeService, 'getBatchChanges').and.returnValue(deferred.promise);
}));
describe('$scope.getBatchChanges', function() {
it('should resolve the promise', inject(function(dnsChangeService) {
this.scope.getBatchChanges()
.then(function(response) {
expect(response.data.batchChanges).toBe(mockBatchChanges)
});
expect(dnsChangeService.getBatchChanges).toHaveBeenCalled();
var mockBatchChanges = {
comments: "this is hopefully a full failure",
createdTimestamp: "Fri May 18 2018 15:01:41",
id: "5bcfd2fe-81d8-4eae-897f-aa8a7c9cbc00",
singleChangeCount: 1,
status: "Failed",
userId: "17350028-b2b8-428d-9f10-dbb518a0364d",
userName:"bwrigh833"
};
deferred.resolve({data: {batchChanges: mockBatchChanges}});
this.rootScope.$apply();
}));
});
});
describe('Service: batchChange', function() {
beforeEach(inject(function ($httpBackend, batchChangeService) {
this.dnsChangeService = batchChangeService;
this.$httpBackend = $httpBackend;
}));
it('http backend gets called properly when getting a batch changes', function () {
this.$httpBackend.expectGET('/api/dnschanges/123').respond('batch change returned');
this.dnsChangeService.getBatchChange('123')
.then(function(response) {
expect(response.data).toBe('batch change returned');
});
this.$httpBackend.flush();
});
it('http backend gets called properly when creating a batch change', function () {
this.$httpBackend.expectPOST('/api/dnschanges').respond('batch change created');
this.dnsChangeService.createBatchChange({comments: "", changes: [{changeType: "Add", type: "A", ttl: 200}]})
.then(function(response) {
expect(response.data).toBe('batch change created');
});
this.$httpBackend.flush();
});
it('http backend gets called properly when approving a batch change', function () {
this.$httpBackend.expectPOST('/api/dnschanges/123/approve').respond('batch change created');
this.dnsChangeService.approveBatchChange("123", "good")
.then(function(response) {
expect(response.data).toBe('batch change created');
});
this.$httpBackend.flush();
});
it('http backend gets called properly when rejecting a batch change', function () {
this.$httpBackend.expectPOST('/api/dnschanges/123/reject').respond('batch change created');
this.dnsChangeService.rejectBatchChange("123", "bad")
.then(function(response) {
expect(response.data).toBe('batch change created');
});
this.$httpBackend.flush();
});
});
describe('Directive: FQDN validation', function(){
var form;
beforeEach(inject(function($compile, $rootScope) {
this.rootScope = $rootScope;
this.scope = $rootScope.$new();
var element = angular.element(
'
'
);
this.scope.change = { fqdn: null };
$compile(element)(this.scope);
form = this.scope.form;
}));
it('passes with at least one dot', function(){
form.cname.$setViewValue('test.com');
this.scope.$digest();
expect(this.scope.change.cname).toEqual('test.com');
expect(form.cname.$valid).toBe(true);
});
it('passes with trailing dot', function(){
form.cname.$setViewValue('test.');
this.scope.$digest();
expect(this.scope.change.cname).toEqual('test.');
expect(form.cname.$valid).toBe(true);
});
it('fails without any dots', function(){
form.cname.$setViewValue('invalidfqdn');
this.scope.$digest();
expect(this.scope.change.cname).toBeUndefined();
expect(form.cname.$valid).toBe(false);
});
});
describe('Directive: Invalid IPv4 validation for CNAME record data', function(){
var form;
beforeEach(inject(function($compile, $rootScope) {
this.rootScope = $rootScope;
this.scope = $rootScope.$new();
var element = angular.element(
''
);
this.scope.change = { fqdn: null };
$compile(element)(this.scope);
form = this.scope.form;
}));
it('fails with an IPV4 address', function(){
form.cname.$setViewValue('1.1.1.1');
this.scope.$digest();
expect(this.scope.change.cname).toBeUndefined();
expect(form.cname.$valid).toBe(false);
});
it('fails with an IPV4 address and a trailing dot', function(){
form.cname.$setViewValue('test.');
this.scope.$digest();
expect(this.scope.change.cname).toBeUndefined();
expect(form.cname.$valid).toBe(false);
});
it('passes if not given an IPV4 address', function(){
form.cname.$setViewValue('notanIP');
this.scope.$digest();
expect(this.scope.change.cname).toEqual('notanIP');
expect(form.cname.$valid).toBe(true);
});
});
describe('Directive: combined CNAME record data validations', function(){
var form;
beforeEach(inject(function($compile, $rootScope) {
this.rootScope = $rootScope;
this.scope = $rootScope.$new();
var element = angular.element(
''
);
this.scope.change = { fqdn: null };
$compile(element)(this.scope);
form = this.scope.form;
}));
it('passes with at least one dot and not an IP Address', function(){
form.cname.$setViewValue('test.com');
this.scope.$digest();
expect(this.scope.change.cname).toEqual('test.com');
expect(form.cname.$valid).toBe(false);
});
it('fails with an IP Address', function(){
form.cname.$setViewValue('1.1.1.1');
this.scope.$digest();
expect(this.scope.change.cname).toBeUndefined();
expect(form.cname.$valid).toBe(false);
});
it('fails without at least one dot', function(){
form.cname.$setViewValue('testcom');
this.scope.$digest();
expect(this.scope.change.cname).toBeUndefined();
expect(form.cname.$valid).toBe(false);
});
});
describe('Directive: IPv4 validation', function(){
var form;
beforeEach(inject(function($compile, $rootScope) {
this.rootScope = $rootScope;
this.scope = $rootScope.$new();
var element = angular.element(
''
);
this.scope.change = { address: null };
$compile(element)(this.scope);
form = this.scope.form;
}));
it('passes with correct IPv4 format', function(){
form.address.$setViewValue('1.1.1.1');
this.scope.$digest();
expect(this.scope.change.address).toEqual('1.1.1.1');
expect(form.address.$valid).toBe(true);
});
it('fails with incorrect IPv4 format', function(){
form.address.$setViewValue('bad.ipv4');
this.scope.$digest();
expect(this.scope.change.address).toBeUndefined();
expect(form.address.$valid).toBe(false);
});
});
describe('Directive: IPv6 validation', function(){
var form;
beforeEach(inject(function($compile, $rootScope) {
this.rootScope = $rootScope;
this.scope = $rootScope.$new();
var element = angular.element(
''
);
this.scope.change = { address: null };
$compile(element)(this.scope);
form = this.scope.form;
}));
it('passes with correct IPv6 format', function(){
form.address.$setViewValue('fd69:27cc:fe91::60');
this.scope.$digest();
expect(this.scope.change.address).toEqual('fd69:27cc:fe91::60');
expect(form.address.$valid).toBe(true);
});
it('fails with incorrect IPv6 format', function(){
form.address.$setViewValue('bad.ipv6');
this.scope.$digest();
expect(this.scope.change.address).toBeUndefined();
expect(form.address.$valid).toBe(false);
});
});
});