/**
 * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
 * SPDX-FileCopyrightText: 2014 ownCloud Inc.
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

/* global dav */

describe('Backbone Webdav extension', function() {
	var davClientRequestStub;
	var davClientPropPatchStub;
	var davClientPropFindStub;
	var deferredRequest;

	beforeEach(function() {
		deferredRequest = $.Deferred();
		davClientRequestStub = sinon.stub(dav.Client.prototype, 'request');
		davClientPropPatchStub = sinon.stub(dav.Client.prototype, 'propPatch');
		davClientPropFindStub = sinon.stub(dav.Client.prototype, 'propFind');
		davClientRequestStub.returns(deferredRequest.promise());
		davClientPropPatchStub.returns(deferredRequest.promise());
		davClientPropFindStub.returns(deferredRequest.promise());
	});
	afterEach(function() {
		davClientRequestStub.restore();
		davClientPropPatchStub.restore();
		davClientPropFindStub.restore();
	});

	describe('collections', function() {
		var TestModel;
		var TestCollection;
		beforeEach(function() {
			TestModel = OC.Backbone.Model.extend({
				sync: OC.Backbone.davSync,
				davProperties: {
					'firstName': '{http://owncloud.org/ns}first-name',
					'lastName': '{http://owncloud.org/ns}last-name',
					'age': '{http://owncloud.org/ns}age',
					'married': '{http://owncloud.org/ns}married'
				},
				parse: function(data) {
					return {
						id: data.id,
						firstName: data.firstName,
						lastName: data.lastName,
						age: parseInt(data.age, 10),
						married: data.married === 'true' || data.married === true
					};
				}
			});
			TestCollection = OC.Backbone.Collection.extend({
				sync: OC.Backbone.davSync,
				model: TestModel,
				url: 'http://example.com/owncloud/remote.php/test/'
			});
		});

		it('makes a POST request to create model into collection', function(done) {
			var collection = new TestCollection();
			var model = collection.create({
				firstName: 'Hello',
				lastName: 'World'
			});

			expect(davClientRequestStub.calledOnce).toEqual(true);
			expect(davClientRequestStub.getCall(0).args[0])
				.toEqual('POST');
			expect(davClientRequestStub.getCall(0).args[1])
				.toEqual('http://example.com/owncloud/remote.php/test/');
			expect(davClientRequestStub.getCall(0).args[2]['Content-Type'])
				.toEqual('application/json');
			expect(davClientRequestStub.getCall(0).args[2]['X-Requested-With'])
				.toEqual('XMLHttpRequest');
			expect(davClientRequestStub.getCall(0).args[3])
				.toEqual(JSON.stringify({
					'firstName': 'Hello',
					'lastName': 'World'
				}));

			var responseHeaderStub = sinon.stub()
				.withArgs('Content-Location')
				.returns('http://example.com/owncloud/remote.php/test/123');
			deferredRequest.resolve({
				status: 201,
				body: '',
				xhr: {
					getResponseHeader: responseHeaderStub
				}
			});

			setTimeout(function() {
				expect(model.id).toEqual('123');

				done();
			}, 0)
		});

		it('uses PROPFIND to retrieve collection', function(done) {
			var successStub = sinon.stub();
			var errorStub = sinon.stub();
			var collection = new TestCollection();
			collection.fetch({
				success: successStub,
				error: errorStub
			});

			expect(davClientPropFindStub.calledOnce).toEqual(true);
			expect(davClientPropFindStub.getCall(0).args[0])
				.toEqual('http://example.com/owncloud/remote.php/test/');
			expect(davClientPropFindStub.getCall(0).args[1])
				.toEqual([
					'{http://owncloud.org/ns}first-name',
					'{http://owncloud.org/ns}last-name',
					'{http://owncloud.org/ns}age',
					'{http://owncloud.org/ns}married'
				]);
			expect(davClientPropFindStub.getCall(0).args[2])
				.toEqual(1);
			expect(davClientPropFindStub.getCall(0).args[3]['X-Requested-With'])
				.toEqual('XMLHttpRequest');

			deferredRequest.resolve({
				status: 207,
				body: [
					// root element
					{
						href: 'http://example.org/owncloud/remote.php/test/',
						propStat: []
					},
					// first model
					{
						href: 'http://example.org/owncloud/remote.php/test/123',
						propStat: [{
							status: 'HTTP/1.1 200 OK',
							properties: {
								'{http://owncloud.org/ns}first-name': 'Hello',
								'{http://owncloud.org/ns}last-name': 'World'
							}
						}]
					},
					// second model
					{
						href: 'http://example.org/owncloud/remote.php/test/456',
						propStat: [{
							status: 'HTTP/1.1 200 OK',
							properties: {
								'{http://owncloud.org/ns}first-name': 'Test',
								'{http://owncloud.org/ns}last-name': 'Person'
							}
						}]
					}
				]
			});

			setTimeout(function() {
				expect(collection.length).toEqual(2);

				var model = collection.get('123');
				expect(model.id).toEqual('123');
				expect(model.get('firstName')).toEqual('Hello');
				expect(model.get('lastName')).toEqual('World');

				model = collection.get('456');
				expect(model.id).toEqual('456');
				expect(model.get('firstName')).toEqual('Test');
				expect(model.get('lastName')).toEqual('Person');

				expect(successStub.calledOnce).toEqual(true);
				expect(errorStub.notCalled).toEqual(true);

				done();
			}, 0)
		});

		function testMethodError(doCall, done) {
			var successStub = sinon.stub();
			var errorStub = sinon.stub();

			doCall(successStub, errorStub);

			deferredRequest.resolve({
				status: 404,
				body: ''
			});

			setTimeout(function() {
				expect(successStub.notCalled).toEqual(true);
				expect(errorStub.calledOnce).toEqual(true);

				done();
			}, 0)
		}

		it('calls error handler if error status in PROPFIND response', function(done) {
			testMethodError(function(success, error) {
				var collection = new TestCollection();
				collection.fetch({
					success: success,
					error: error
				});
			}, done);
		});
		it('calls error handler if error status in POST response', function(done) {
			testMethodError(function(success, error) {
				var collection = new TestCollection();
				collection.create({
					firstName: 'Hello',
					lastName: 'World'
				}, {
					success: success,
					error: error
				});
			}, done);
		});
	});
	describe('models', function() {
		var TestModel;
		beforeEach(function() {
			TestModel = OC.Backbone.Model.extend({
				sync: OC.Backbone.davSync,
				davProperties: {
					'firstName': '{http://owncloud.org/ns}first-name',
					'lastName': '{http://owncloud.org/ns}last-name',
					'age': '{http://owncloud.org/ns}age', // int
					'married': '{http://owncloud.org/ns}married', // bool
				},
				url: function() {
					return 'http://example.com/owncloud/remote.php/test/' + this.id;
				},
				parse: function(data) {
					return {
						id: data.id,
						firstName: data.firstName,
						lastName: data.lastName,
						age: parseInt(data.age, 10),
						married: data.married === 'true' || data.married === true
					};
				}
			});
		});

		it('makes a PROPPATCH request to update model', function() {
			var model = new TestModel({
				id: '123',
				firstName: 'Hello',
				lastName: 'World',
				age: 32,
				married: false
			});

			model.save({
				firstName: 'Hey',
				age: 33,
				married: true
			});

			expect(davClientPropPatchStub.calledOnce).toEqual(true);
			expect(davClientPropPatchStub.getCall(0).args[0])
				.toEqual('http://example.com/owncloud/remote.php/test/123');
			expect(davClientPropPatchStub.getCall(0).args[1])
				.toEqual({
					'{http://owncloud.org/ns}first-name': 'Hey',
					'{http://owncloud.org/ns}age': '33',
					'{http://owncloud.org/ns}married': 'true'
				});
			expect(davClientPropPatchStub.getCall(0).args[2]['X-Requested-With'])
				.toEqual('XMLHttpRequest');

			deferredRequest.resolve({
				status: 201,
				body: ''
			});

			expect(model.id).toEqual('123');
			expect(model.get('firstName')).toEqual('Hey');
			expect(model.get('age')).toEqual(33);
			expect(model.get('married')).toEqual(true);
		});

		it('uses PROPFIND to fetch single model', function(done) {
			var model = new TestModel({
				id: '123'
			});

			model.fetch();

			expect(davClientPropFindStub.calledOnce).toEqual(true);
			expect(davClientPropFindStub.getCall(0).args[0])
				.toEqual('http://example.com/owncloud/remote.php/test/123');
			expect(davClientPropFindStub.getCall(0).args[1])
				.toEqual([
					'{http://owncloud.org/ns}first-name',
					'{http://owncloud.org/ns}last-name',
					'{http://owncloud.org/ns}age',
					'{http://owncloud.org/ns}married'
				]);
			expect(davClientPropFindStub.getCall(0).args[2])
				.toEqual(0);
			expect(davClientPropFindStub.getCall(0).args[3]['X-Requested-With'])
				.toEqual('XMLHttpRequest');

			deferredRequest.resolve({
				status: 207,
				body: {
					href: 'http://example.org/owncloud/remote.php/test/123',
					propStat: [{
						status: 'HTTP/1.1 200 OK',
						properties: {
							'{http://owncloud.org/ns}first-name': 'Hello',
							'{http://owncloud.org/ns}last-name': 'World',
							'{http://owncloud.org/ns}age': '35',
							'{http://owncloud.org/ns}married': 'true'
						}
					}]
				}
			});

			setTimeout(function() {
				expect(model.id).toEqual('123');
				expect(model.get('firstName')).toEqual('Hello');
				expect(model.get('lastName')).toEqual('World');
				expect(model.get('age')).toEqual(35);
				expect(model.get('married')).toEqual(true);

				done();
			});
		});
		it('makes a DELETE request to destroy model', function() {
			var model = new TestModel({
				id: '123',
				firstName: 'Hello',
				lastName: 'World'
			});

			model.destroy();

			expect(davClientRequestStub.calledOnce).toEqual(true);
			expect(davClientRequestStub.getCall(0).args[0])
				.toEqual('DELETE');
			expect(davClientRequestStub.getCall(0).args[1])
				.toEqual('http://example.com/owncloud/remote.php/test/123');
			expect(davClientRequestStub.getCall(0).args[2]['X-Requested-With'])
				.toEqual('XMLHttpRequest');
			expect(davClientRequestStub.getCall(0).args[3])
				.toBeFalsy();

			deferredRequest.resolve({
				status: 200,
				body: ''
			});
		});

		function testMethodError(doCall, done) {
			var successStub = sinon.stub();
			var errorStub = sinon.stub();

			doCall(successStub, errorStub);

			deferredRequest.resolve({
				status: 404,
				body: ''
			});

			setTimeout(function() {
				expect(successStub.notCalled).toEqual(true);
				expect(errorStub.calledOnce).toEqual(true);

				done();
			});
		}

		it('calls error handler if error status in PROPFIND response', function(done) {
			testMethodError(function(success, error) {
				var model = new TestModel();
				model.fetch({
					success: success,
					error: error
				});
			}, done);
		});
		it('calls error handler if error status in PROPPATCH response', function(done) {
			testMethodError(function(success, error) {
				var model = new TestModel();
				model.save({
					firstName: 'Hey'
				}, {
					success: success,
					error: error
				});
			}, done);
		});
	});
});

