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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install
- run: npm run build
- run: npm run typecheck
69 changes: 39 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@authorizerdev/authorizer-vue",
"version": "1.0.0",
"version": "2.0.0",
"description": "authorizer vue sdk",
"files": [
"dist"
Expand All @@ -13,9 +13,9 @@
"build": "vite build && npm run build:types",
"build:types": "vue-tsc --project tsconfig.build-types.json --declaration --emitDeclarationOnly --outDir dist/types ",
"typecheck": "vue-tsc --project tsconfig.build-types.json --noEmit",
"test": "vue-tsc --project tsconfig.build-types.json --noEmit",
"lint": "prettier --plugin-search-dir . --check . --ignore-path .gitignore && eslint . --ignore-path .gitignore",
"format": "prettier --plugin-search-dir . --write . --ignore-path .gitignore",
"prepare": "husky install"
"format": "prettier --plugin-search-dir . --write . --ignore-path .gitignore"
},
"keywords": [],
"author": "Lakhan Samani",
Expand Down Expand Up @@ -47,6 +47,6 @@
"vue-tsc": "^1.4.2"
},
"dependencies": {
"@authorizerdev/authorizer-js": "^1.2.3"
"@authorizerdev/authorizer-js": "^3.2.1"
}
}
112 changes: 112 additions & 0 deletions src/__tests__/AuthorizerBasicAuthLogin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import AuthorizerBasicAuthLogin from '../components/AuthorizerBasicAuthLogin.vue';
import globalContext from '../state/globalContext';
import globalConfig from '../state/globalConfig';

const { mockLogin, mockVerifyOtp, mockResendOtp } = vi.hoisted(() => ({
mockLogin: vi.fn(),
mockVerifyOtp: vi.fn(),
mockResendOtp: vi.fn()
}));

vi.mock('@authorizerdev/authorizer-js', () => {
const MockAuthorizer = vi.fn().mockImplementation(() => ({
login: mockLogin,
verifyOtp: mockVerifyOtp,
resendOtp: mockResendOtp,
getMetaData: vi.fn().mockResolvedValue({ data: {}, errors: null }),
getSession: vi.fn().mockResolvedValue({ data: null, errors: null })
}));
return { Authorizer: MockAuthorizer };
});

describe('AuthorizerBasicAuthLogin', () => {
beforeEach(() => {
vi.clearAllMocks();
Object.assign(globalConfig, {
is_sign_up_enabled: true,
is_basic_authentication_enabled: true,
is_strong_password_enabled: false
});
globalContext.setAuthData = vi.fn();
});

it('renders email and password fields', () => {
const wrapper = mount(AuthorizerBasicAuthLogin);
expect(wrapper.find('#authorizer-login-email').exists()).toBe(true);
expect(wrapper.find('#authorizer-login-password').exists()).toBe(true);
});

it('validates empty email', async () => {
const wrapper = mount(AuthorizerBasicAuthLogin);
const emailInput = wrapper.find('#authorizer-login-email');
await emailInput.setValue('');
expect(wrapper.text()).toContain('Email is required');
});

it('validates invalid email', async () => {
const wrapper = mount(AuthorizerBasicAuthLogin);
const emailInput = wrapper.find('#authorizer-login-email');
await emailInput.setValue('not-an-email');
expect(wrapper.text()).toContain('Please enter valid email');
});

it('validates empty password', async () => {
const wrapper = mount(AuthorizerBasicAuthLogin);
const passwordInput = wrapper.find('#authorizer-login-password');
await passwordInput.setValue('');
expect(wrapper.text()).toContain('Password is required');
});

it('calls login on form submit and handles success', async () => {
const mockUser = { id: '1', email: 'test@test.com' };
const mockToken = { access_token: 'tok', expires_in: 3600, user: mockUser };
mockLogin.mockResolvedValueOnce({ data: mockToken, errors: null });

const onLogin = vi.fn();
const wrapper = mount(AuthorizerBasicAuthLogin, {
props: { onLogin }
});

await wrapper.find('#authorizer-login-email').setValue('test@test.com');
await wrapper.find('#authorizer-login-password').setValue('password123');
await wrapper.find('form').trigger('submit');
await flushPromises();

expect(mockLogin).toHaveBeenCalledWith(
expect.objectContaining({ email: 'test@test.com', password: 'password123' })
);
expect(onLogin).toHaveBeenCalledWith(mockToken);
});

it('handles login errors', async () => {
mockLogin.mockResolvedValueOnce({
data: null,
errors: [{ message: 'Invalid credentials' }]
});

const wrapper = mount(AuthorizerBasicAuthLogin);
await wrapper.find('#authorizer-login-email').setValue('test@test.com');
await wrapper.find('#authorizer-login-password').setValue('wrong');
await wrapper.find('form').trigger('submit');
await flushPromises();

expect(wrapper.text()).toContain('Invalid credentials');
});

it('shows OTP screen when MFA is required', async () => {
mockLogin.mockResolvedValueOnce({
data: { should_show_email_otp_screen: true },
errors: null
});

const wrapper = mount(AuthorizerBasicAuthLogin);
await wrapper.find('#authorizer-login-email').setValue('test@test.com');
await wrapper.find('#authorizer-login-password').setValue('password123');
await wrapper.find('form').trigger('submit');
await flushPromises();

expect(wrapper.find('#authorizer-verify-otp').exists()).toBe(true);
});
});
68 changes: 68 additions & 0 deletions src/__tests__/AuthorizerForgotPassword.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import AuthorizerForgotPassword from '../components/AuthorizerForgotPassword.vue';
import globalConfig from '../state/globalConfig';

const { mockForgotPassword } = vi.hoisted(() => ({
mockForgotPassword: vi.fn()
}));

vi.mock('@authorizerdev/authorizer-js', () => {
const MockAuthorizer = vi.fn().mockImplementation(() => ({
forgotPassword: mockForgotPassword,
getMetaData: vi.fn().mockResolvedValue({ data: {}, errors: null }),
getSession: vi.fn().mockResolvedValue({ data: null, errors: null })
}));
return { Authorizer: MockAuthorizer };
});

describe('AuthorizerForgotPassword', () => {
beforeEach(() => {
vi.clearAllMocks();
Object.assign(globalConfig, {
redirectURL: 'http://localhost:3000'
});
});

it('renders email field', () => {
const wrapper = mount(AuthorizerForgotPassword);
expect(wrapper.find('#authorizer-forgot-password-email').exists()).toBe(true);
});

it('validates empty email', async () => {
const wrapper = mount(AuthorizerForgotPassword);
await wrapper.find('#authorizer-forgot-password-email').setValue('');
expect(wrapper.text()).toContain('Email is required');
});

it('calls forgotPassword and shows success message', async () => {
mockForgotPassword.mockResolvedValueOnce({
data: { message: 'Reset link sent to your email' },
errors: null
});

const wrapper = mount(AuthorizerForgotPassword);
await wrapper.find('#authorizer-forgot-password-email').setValue('test@test.com');
await wrapper.find('form').trigger('submit');
await flushPromises();

expect(mockForgotPassword).toHaveBeenCalledWith(
expect.objectContaining({ email: 'test@test.com' })
);
expect(wrapper.text()).toContain('Reset link sent to your email');
});

it('shows error on forgotPassword failure', async () => {
mockForgotPassword.mockResolvedValueOnce({
data: null,
errors: [{ message: 'User not found' }]
});

const wrapper = mount(AuthorizerForgotPassword);
await wrapper.find('#authorizer-forgot-password-email').setValue('test@test.com');
await wrapper.find('form').trigger('submit');
await flushPromises();

expect(wrapper.text()).toContain('User not found');
});
});
Loading
Loading