sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Authorization/AuthorizationMiddlewear.js"
SandboxedModule = require('sandboxed-module')
Errors = require "../../../../app/js/Features/Errors/Errors.js"

describe "AuthorizationMiddlewear", ->
	beforeEach ->
		@user_id = "user-id-123"
		@project_id = "project-id-123"
		@token = 'some-token'
		@AuthenticationController =
			getLoggedInUserId: sinon.stub().returns(@user_id)
			isUserLoggedIn: sinon.stub().returns(true)
		@AuthorizationMiddlewear = SandboxedModule.require modulePath, requires:
			"./AuthorizationManager": @AuthorizationManager = {}
			"logger-sharelatex": {log: () ->}
			"mongojs": ObjectId: @ObjectId = {}
			"../Errors/Errors": Errors
			'../Authentication/AuthenticationController': @AuthenticationController
			"../TokenAccess/TokenAccessHandler": @TokenAccessHandler =
				getRequestToken: sinon.stub().returns(@token)
		@req = {}
		@res = {}
		@ObjectId.isValid = sinon.stub()
		@ObjectId.isValid.withArgs(@project_id).returns true
		@next = sinon.stub()

	METHODS_TO_TEST = {
		"ensureUserCanReadProject": "canUserReadProject"
		"ensureUserCanWriteProjectSettings": "canUserWriteProjectSettings"
		"ensureUserCanWriteProjectContent": "canUserWriteProjectContent"
		"ensureUserCanAdminProject": "canUserAdminProject"
	}
	for middlewearMethod, managerMethod of METHODS_TO_TEST
		do (middlewearMethod, managerMethod) ->
			describe middlewearMethod, ->
				beforeEach ->
					@req.params =
						project_id: @project_id
					@AuthorizationManager[managerMethod] = sinon.stub()
					@AuthorizationMiddlewear.redirectToRestricted = sinon.stub()

				describe "with missing project_id", ->
					beforeEach ->
						@req.params = {}

					it "should return an error to next", ->
						@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
						@next.calledWith(new Error()).should.equal true

				describe "with logged in user", ->
					beforeEach ->
						@AuthenticationController.getLoggedInUserId.returns(@user_id)

					describe "when user has permission", ->
						beforeEach ->
							@AuthorizationManager[managerMethod]
								.withArgs(@user_id, @project_id, @token)
								.yields(null, true)

						it "should return next", ->
							@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
							@next.called.should.equal true

					describe "when user doesn't have permission", ->
						beforeEach ->
							@AuthorizationManager[managerMethod]
								.withArgs(@user_id, @project_id, @token)
								.yields(null, false)

						it "should redirect to redirectToRestricted", ->
							@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
							@next.called.should.equal false
							@AuthorizationMiddlewear.redirectToRestricted
								.calledWith(@req, @res, @next)
								.should.equal true

				describe "with anonymous user", ->
					describe "when user has permission", ->
						beforeEach ->
							@AuthenticationController.getLoggedInUserId.returns(null)
							@AuthorizationManager[managerMethod]
								.withArgs(null, @project_id, @token)
								.yields(null, true)

						it "should return next", ->
							@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
							@next.called.should.equal true

					describe "when user doesn't have permission", ->
						beforeEach ->
							@AuthenticationController.getLoggedInUserId.returns(null)
							@AuthorizationManager[managerMethod]
								.withArgs(null, @project_id, @token)
								.yields(null, false)

						it "should redirect to redirectToRestricted", ->
							@AuthorizationMiddlewear[middlewearMethod] @req, @res, @next
							@next.called.should.equal false
							@AuthorizationMiddlewear.redirectToRestricted
								.calledWith(@req, @res, @next)
								.should.equal true

				describe "with malformed project id", ->
					beforeEach ->
						@req.params =
							project_id: "blah"
						@ObjectId.isValid = sinon.stub().returns false

					it "should return a not found error", (done) ->
						@AuthorizationMiddlewear[middlewearMethod] @req, @res, (error) ->
							error.should.be.instanceof Errors.NotFoundError
							done()

	describe "ensureUserIsSiteAdmin", ->
		beforeEach ->
			@AuthorizationManager.isUserSiteAdmin = sinon.stub()
			@AuthorizationMiddlewear.redirectToRestricted = sinon.stub()

		describe "with logged in user", ->
			beforeEach ->
				@AuthenticationController.getLoggedInUserId.returns(@user_id)

			describe "when user has permission", ->
				beforeEach ->
					@AuthorizationManager.isUserSiteAdmin
						.withArgs(@user_id)
						.yields(null, true)

				it "should return next", ->
					@AuthorizationMiddlewear.ensureUserIsSiteAdmin @req, @res, @next
					@next.called.should.equal true

			describe "when user doesn't have permission", ->
				beforeEach ->
					@AuthorizationManager.isUserSiteAdmin
						.withArgs(@user_id)
						.yields(null, false)

				it "should redirect to redirectToRestricted", ->
					@AuthorizationMiddlewear.ensureUserIsSiteAdmin @req, @res, @next
					@next.called.should.equal false
					@AuthorizationMiddlewear.redirectToRestricted
						.calledWith(@req, @res, @next)
						.should.equal true

		describe "with anonymous user", ->
			describe "when user has permission", ->
				beforeEach ->
					@AuthenticationController.getLoggedInUserId.returns(null)
					@AuthorizationManager.isUserSiteAdmin
						.withArgs(null)
						.yields(null, true)

				it "should return next", ->
					@AuthorizationMiddlewear.ensureUserIsSiteAdmin @req, @res, @next
					@next.called.should.equal true

			describe "when user doesn't have permission", ->
				beforeEach ->
					@AuthenticationController.getLoggedInUserId.returns(null)
					@AuthorizationManager.isUserSiteAdmin
						.withArgs(null)
						.yields(null, false)

				it "should redirect to redirectToRestricted", ->
					@AuthorizationMiddlewear.ensureUserIsSiteAdmin @req, @res, @next
					@next.called.should.equal false
					@AuthorizationMiddlewear.redirectToRestricted
						.calledWith(@req, @res, @next)
						.should.equal true

	describe "ensureUserCanReadMultipleProjects", ->
		beforeEach ->
			@AuthorizationManager.canUserReadProject = sinon.stub()
			@AuthorizationMiddlewear.redirectToRestricted = sinon.stub()
			@req.query =
				project_ids: "project1,project2"

		describe "with logged in user", ->
			beforeEach ->
				@AuthenticationController.getLoggedInUserId.returns(@user_id)

			describe "when user has permission to access all projects", ->
				beforeEach ->
					@AuthorizationManager.canUserReadProject
						.withArgs(@user_id, "project1", @token)
						.yields(null, true)
					@AuthorizationManager.canUserReadProject
						.withArgs(@user_id, "project2", @token)
						.yields(null, true)

				it "should return next", ->
					@AuthorizationMiddlewear.ensureUserCanReadMultipleProjects @req, @res, @next
					@next.called.should.equal true

			describe "when user doesn't have permission to access one of the projects", ->
				beforeEach ->
					@AuthorizationManager.canUserReadProject
						.withArgs(@user_id, "project1", @token)
						.yields(null, true)
					@AuthorizationManager.canUserReadProject
						.withArgs(@user_id, "project2", @token)
						.yields(null, false)

				it "should redirect to redirectToRestricted", ->
					@AuthorizationMiddlewear.ensureUserCanReadMultipleProjects @req, @res, @next
					@next.called.should.equal false
					@AuthorizationMiddlewear.redirectToRestricted
						.calledWith(@req, @res, @next)
						.should.equal true

		describe "with anonymous user", ->
			describe "when user has permission", ->
				describe "when user has permission to access all projects", ->
					beforeEach ->
						@AuthenticationController.getLoggedInUserId.returns(null)
						@AuthorizationManager.canUserReadProject
							.withArgs(null, "project1", @token)
							.yields(null, true)
						@AuthorizationManager.canUserReadProject
							.withArgs(null, "project2", @token)
							.yields(null, true)

					it "should return next", ->
						@AuthorizationMiddlewear.ensureUserCanReadMultipleProjects @req, @res, @next
						@next.called.should.equal true

				describe "when user doesn't have permission to access one of the projects", ->
					beforeEach ->
						@AuthenticationController.getLoggedInUserId.returns(null)
						@AuthorizationManager.canUserReadProject
							.withArgs(null, "project1", @token)
							.yields(null, true)
						@AuthorizationManager.canUserReadProject
							.withArgs(null, "project2", @token)
							.yields(null, false)

					it "should redirect to redirectToRestricted", ->
						@AuthorizationMiddlewear.ensureUserCanReadMultipleProjects @req, @res, @next
						@next.called.should.equal false
						@AuthorizationMiddlewear.redirectToRestricted
							.calledWith(@req, @res, @next)
							.should.equal true
