Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defineConfig } from "cypress"

export default defineConfig({
e2e: {
baseUrl: process.env.CYPRESS_BASE_URL || "http://localhost:4500",
setupNodeEvents(on, config) {
// implement node event listeners here
},
Expand Down
100 changes: 93 additions & 7 deletions cypress/e2e/login.cy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { CyHttpMessages } from "cypress/types/net-stubbing"
import IncomingHttpRequest = CyHttpMessages.IncomingHttpRequest

const BASE_URL = 'http://localhost:4500'
const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:4500'
const API_URL = '*login'
const REMEMBER_ME_OPTION_KEY = 'CAPROVER_REMEMBER_ME_OPTION'
const AUTH_KEY = 'CAPROVER_AUTH_KEY'
const FAKE_PASSWORD = 'fake-password'
const EMPTY_PASSWORD_RESPONSE = {"status": 1000, "description": "password is empty.", "data": {}}
const FAKE_PASSWORD_RESPONSE = {"status": 1105, "description": "Password is incorrect.", "data": {}}
Expand All @@ -11,6 +13,14 @@ const REAL_PASSWORD_RESPONSE = {"status": 100, "description": "Login succeeded",

let interceptFlag: boolean = true

const visitLoginPage = () =>
cy.visit(BASE_URL, {
onBeforeLoad: (win) => {
win.localStorage.clear()
win.sessionStorage.clear()
},
})

describe('login page', () => {
describe('without any password', () => {
beforeEach(() => {
Expand All @@ -29,7 +39,7 @@ describe('login page', () => {
})
})
it('should not sent the request to the server', () => {
cy.visit(BASE_URL)
visitLoginPage()
cy.get('button[type=submit]').click()
cy.wait(1000).then(() => {
expect(interceptFlag).to.equal(false)
Expand All @@ -53,7 +63,7 @@ describe('login page', () => {
}).as('logUser')
})
it('should send the request to the server and display Password is incorrect', () => {
cy.visit(BASE_URL)
visitLoginPage()
cy.get('input[type=password]').type(FAKE_PASSWORD, { force: true })
cy.get('button[type=submit]').click()
cy.wait('@logUser').then(() => {
Expand All @@ -67,14 +77,14 @@ describe('login page', () => {
})
describe('trying to change the password display', () => {
it('should change the display of the password to clear with mouse', () => {
cy.visit(BASE_URL)
visitLoginPage()
cy.get('input[type=password]').as('passwordInput')
cy.get('@passwordInput').type(REAL_PASSWORD)
cy.get('.ant-input-suffix').click()
cy.get("input[type='text']").should("have.value", REAL_PASSWORD)
})
it('should change the display of the password to clear then hide with mouse', () => {
cy.visit(BASE_URL)
visitLoginPage()
cy.get('input[type=password]').as('passwordInput')
cy.get('@passwordInput').type(REAL_PASSWORD)
cy.get('.ant-input-suffix').click()
Expand Down Expand Up @@ -102,7 +112,7 @@ describe('login page', () => {
}).as('logUser')
})
it('should send the request to the server and redirect into the dashboard', () => {
cy.visit(BASE_URL)
visitLoginPage()
cy.get('input[type=password]').type(REAL_PASSWORD, { force: true })
cy.get('button[type=submit]').click()
cy.wait('@logUser').then(() => {
Expand All @@ -129,7 +139,7 @@ describe('login page', () => {
}).as('logUser')
})
it('should send the request to the server and redirect into the dashboard', () => {
cy.visit(BASE_URL)
visitLoginPage()
cy.get('input[type=password]').type(REAL_PASSWORD + "{enter}", { force: true })
cy.wait('@logUser').then(() => {
expect(interceptFlag).to.equal(true)
Expand All @@ -138,4 +148,80 @@ describe('login page', () => {
})
})
})

describe('Remember Me preference', () => {
const openRememberMePanel = () => {
cy.contains('.ant-collapse-header', 'Remember Me').click()
}

it('persists the selected option and restores it after reload', () => {
visitLoginPage()
openRememberMePanel()
cy.contains('.ant-radio-wrapper', 'Use localStorage').click()

cy.window().then((win) => {
expect(
win.localStorage.getItem(REMEMBER_ME_OPTION_KEY)
).to.equal('3')
})

cy.reload()
openRememberMePanel()
cy.get('input[type="radio"][value="3"]').should('be.checked')
})

it('stores a successful sessionStorage login in sessionStorage only', () => {
cy.intercept(
{
method: 'POST',
url: API_URL,
},
{
statusCode: 200,
body: REAL_PASSWORD_RESPONSE,
}
).as('rememberMeSessionLogin')

visitLoginPage()
openRememberMePanel()
cy.contains('.ant-radio-wrapper', 'Use sessionStorage').click()
cy.get('input[type=password]').type(REAL_PASSWORD, { force: true })
cy.get('button[type=submit]').click()
cy.wait('@rememberMeSessionLogin')

cy.window().then((win) => {
expect(win.sessionStorage.getItem(AUTH_KEY)).to.equal(
REAL_PASSWORD_RESPONSE.data.token
)
expect(win.localStorage.getItem(AUTH_KEY)).to.equal('')
})
})

it('stores a successful localStorage login in localStorage only', () => {
cy.intercept(
{
method: 'POST',
url: API_URL,
},
{
statusCode: 200,
body: REAL_PASSWORD_RESPONSE,
}
).as('rememberMeLocalLogin')

visitLoginPage()
openRememberMePanel()
cy.contains('.ant-radio-wrapper', 'Use localStorage').click()
cy.get('input[type=password]').type(REAL_PASSWORD, { force: true })
cy.get('button[type=submit]').click()
cy.wait('@rememberMeLocalLogin')

cy.window().then((win) => {
expect(win.localStorage.getItem(AUTH_KEY)).to.equal(
REAL_PASSWORD_RESPONSE.data.token
)
expect(win.sessionStorage.getItem(AUTH_KEY)).to.equal('')
})
})
})
})
125 changes: 125 additions & 0 deletions src/containers/Login.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { fireEvent, render, screen, cleanup } from '@testing-library/react'

jest.mock('../utils/Language', () => ({
isLanguageEnabled: false,
languagesOptions: [],
localize: (_key: string, message: string) => message,
getCurrentLanguageOption: () => ({
value: 'en-US',
rtl: false,
antdLocale: {},
messages: {},
}),
setCurrentLanguageOption: jest.fn(),
}))

import {
LOCAL_STORAGE,
NO_SESSION,
NormalLoginForm,
SESSION_STORAGE,
} from './Login'
import StorageHelper, { REMEMBER_ME_OPTION } from '../utils/StorageHelper'

const getRadio = (option: number): HTMLInputElement => {
const radio = document.querySelector(
`input[type="radio"][value="${option}"]`
)
if (!radio) throw new Error(`Radio option ${option} was not rendered`)
return radio as HTMLInputElement
}

const openRememberMePanel = () => {
fireEvent.click(screen.getByText('Remember Me'))
}

const renderLoginForm = (onLoginRequested = jest.fn()) =>
render(
<NormalLoginForm hasOtp={false} onLoginRequested={onLoginRequested} />
)

describe('Remember Me login form', () => {
beforeAll(() => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: (query: string) => ({
matches: false,
media: query,
onchange: undefined,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
}),
})
})

beforeEach(() => {
window.localStorage.clear()
})

afterEach(() => {
cleanup()
})

it.each([NO_SESSION, SESSION_STORAGE, LOCAL_STORAGE])(
'persists option %i immediately when selected',
(option) => {
StorageHelper.setRememberMeOptionInLocalStorage(
option === NO_SESSION ? SESSION_STORAGE : NO_SESSION
)
renderLoginForm()
openRememberMePanel()

fireEvent.click(getRadio(option))

expect(StorageHelper.getRememberMeOptionFromLocalStorage()).toBe(
option
)
}
)

it('restores the selected option after the form is remounted', () => {
renderLoginForm()
openRememberMePanel()
fireEvent.click(getRadio(LOCAL_STORAGE))
expect(getRadio(LOCAL_STORAGE).checked).toBe(true)

cleanup()
renderLoginForm()
openRememberMePanel()

expect(getRadio(LOCAL_STORAGE).checked).toBe(true)
expect(getRadio(NO_SESSION).checked).toBe(false)
})

it.each([
['no stored option', undefined],
['malformed JSON', '{not-json'],
['unknown option', JSON.stringify(99)],
])('defaults to NO_SESSION for %s', (_description, storedValue) => {
if (storedValue !== undefined) {
window.localStorage.setItem(REMEMBER_ME_OPTION, storedValue)
}

renderLoginForm()
openRememberMePanel()

expect(getRadio(NO_SESSION).checked).toBe(true)
expect(getRadio(SESSION_STORAGE).checked).toBe(false)
expect(getRadio(LOCAL_STORAGE).checked).toBe(false)
})

it('submits the currently selected option to the callback', () => {
const onLoginRequested = jest.fn()
const { container } = renderLoginForm(onLoginRequested)
openRememberMePanel()
fireEvent.click(getRadio(SESSION_STORAGE))

fireEvent.submit(container.querySelector('form') as HTMLFormElement)

expect(onLoginRequested).toHaveBeenCalledTimes(1)
expect(onLoginRequested.mock.calls[0][2]).toBe(SESSION_STORAGE)
})
})
Loading
Loading