Skip to content
Open
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
3 changes: 2 additions & 1 deletion packages/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@testing-library/dom": "^10.4.0",
"@types/node": "^22.15.3",
"@vitest/browser": "^3.1.3",
"@vitest/coverage-v8": "3.0.8",
"@wso2/eslint-plugin": "catalog:",
"@wso2/prettier-config": "catalog:",
"esbuild": "^0.25.9",
Expand Down Expand Up @@ -74,4 +75,4 @@
"publishConfig": {
"access": "public"
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a new line.

Empty file.
199 changes: 199 additions & 0 deletions packages/browser/src/__tests__/AsgardeoBrowserClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/**
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you 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.
*/

/* eslint-disable @typescript-eslint/typedef, class-methods-use-this, no-underscore-dangle, @typescript-eslint/no-unused-vars */

import type {
AllOrganizationsApiResponse,
EmbeddedFlowExecuteRequestPayload,
EmbeddedFlowExecuteResponse,
EmbeddedSignInFlowHandleRequestPayload,
Organization,
SignInOptions,
SignOutOptions,
SignUpOptions,
Storage,
TokenExchangeRequestConfig,
TokenResponse,
User,
UserProfile,
} from '@asgardeo/javascript';
import {AsgardeoJavaScriptClient} from '@asgardeo/javascript';
import {describe, it, expect} from 'vitest';
import AsgardeoBrowserClient from '../AsgardeoBrowserClient';
import {AsgardeoBrowserConfig} from '../models/config';

class TestBrowserClient extends AsgardeoBrowserClient<AsgardeoBrowserConfig> {
private _config!: AsgardeoBrowserConfig;

private _loading = false;

async switchOrganization(_organization: Organization, _sessionId?: string): Promise<TokenResponse | Response> {
return {accessToken: 'token'} as TokenResponse;
}

async initialize(config: AsgardeoBrowserConfig, _storage?: Storage): Promise<boolean> {
this._config = config;
this._loading = false;
return true;
}

async reInitialize(_config: Partial<AsgardeoBrowserConfig>): Promise<boolean> {
return true;
}

async getUser(_options?: any): Promise<User> {
return {id: 'u1'} as unknown as User;
}

async getAllOrganizations(_options?: any, _sessionId?: string): Promise<AllOrganizationsApiResponse> {
return {hasMore: false, organizations: []} as AllOrganizationsApiResponse;
}

async getMyOrganizations(_options?: any, _sessionId?: string): Promise<Organization[]> {
return [] as Organization[];
}

async getCurrentOrganization(_sessionId?: string): Promise<Organization | null> {
return null;
}

async getUserProfile(_options?: any): Promise<UserProfile> {
return {id: 'u1'} as unknown as UserProfile;
}

isLoading(): boolean {
return this._loading;
}

async isSignedIn(): Promise<boolean> {
return true;
}

async updateUserProfile(_payload: any, _userId?: string): Promise<User> {
return {id: 'u1'} as unknown as User;
}

getConfiguration(): AsgardeoBrowserConfig {
return this._config;
}

async exchangeToken(_config: TokenExchangeRequestConfig, _sessionId?: string): Promise<TokenResponse | Response> {
return {accessToken: 'token'} as TokenResponse;
}

// signIn overloads
async signIn(
_options?: SignInOptions,
_sessionId?: string,
_onSignInSuccess?: (afterSignInUrl: string) => void,
): Promise<User>;
async signIn(
_payload: EmbeddedSignInFlowHandleRequestPayload,
_request: Request,
_sessionId?: string,
_onSignInSuccess?: (afterSignInUrl: string) => void,
): Promise<User>;
async signIn(): Promise<User> {
return {id: 'u1'} as unknown as User;
}

async signInSilently(_options?: SignInOptions): Promise<User | boolean> {
return false;
}

// signOut overloads
async signOut(_options?: SignOutOptions, _afterSignOut?: (afterSignOutUrl: string) => void): Promise<string>;
async signOut(
_options?: SignOutOptions,
_sessionId?: string,
_afterSignOut?: (afterSignOutUrl: string) => void,
): Promise<string>;
async signOut(): Promise<string> {
return 'signed-out';
}

// signUp overloads
async signUp(_options?: SignUpOptions): Promise<void>;
async signUp(_payload: EmbeddedFlowExecuteRequestPayload): Promise<EmbeddedFlowExecuteResponse>;
async signUp(): Promise<void | EmbeddedFlowExecuteResponse> {
return undefined;
}

async getAccessToken(_sessionId?: string): Promise<string> {
return 'token';
}

clearSession(_sessionId?: string): void {}
}

describe('AsgardeoBrowserClient', () => {
it('should be a class export (default)', () => {
expect(typeof AsgardeoBrowserClient).toBe('function');
});

it('should allow creating a concrete subclass instance', async () => {
const client: TestBrowserClient = new TestBrowserClient();
expect(client).toBeInstanceOf(TestBrowserClient);
// Inheritance check against the base JS client
expect(client).toBeInstanceOf(AsgardeoJavaScriptClient as unknown as Function);

const config: AsgardeoBrowserConfig = {
baseUrl: 'https://example.org/t/acme',
clientId: 'abc',
storage: 'browserMemory',
};

const initialized: boolean = await client.initialize(config);
expect(initialized).toBe(true);
expect(client.getConfiguration()).toEqual(config);
});

it('should return stubbed values for core methods', async () => {
const client: TestBrowserClient = new TestBrowserClient();
await client.initialize({baseUrl: 'https://x', clientId: 'y', storage: 'browserMemory'});

expect(client.isLoading()).toBe(false);
await expect(client.isSignedIn()).resolves.toBe(true);

await expect(client.getUser()).resolves.toBeTruthy();
await expect(client.getUserProfile()).resolves.toBeTruthy();

await expect(client.getMyOrganizations()).resolves.toEqual([]);
await expect(client.getAllOrganizations()).resolves.toMatchObject({hasMore: false});
await expect(client.getCurrentOrganization()).resolves.toBeNull();

await expect(client.getAccessToken()).resolves.toBe('token');
await expect(
client.exchangeToken({
attachToken: false,
data: {},
id: 'req-1',
returnsSession: false,
signInRequired: false,
} as TokenExchangeRequestConfig),
).resolves.toBeTruthy();

await expect(client.signIn()).resolves.toBeTruthy();
await expect(client.signInSilently()).resolves.toBe(false);
await expect(client.signOut()).resolves.toBe('signed-out');

// signUp returns void in our stub
await expect(client.signUp()).resolves.toBeUndefined();
});
});
109 changes: 109 additions & 0 deletions packages/browser/src/models/__tests__/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you 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.
*/

import { AsgardeoBrowserConfig } from '../config';

describe('AsgardeoBrowserConfig', () => {
const validStorageTypes: readonly ['sessionStorage', 'localStorage', 'browserMemory', 'webWorker'] = [
'sessionStorage',
'localStorage',
'browserMemory',
'webWorker',
];
const TEST_BASE_URL: string = 'https://localhost:9443';

describe('Required Fields', () => {
it('should require baseUrl and clientId', () => {
const config: AsgardeoBrowserConfig = {
baseUrl: TEST_BASE_URL,
clientId: 'client123',
};
expect(config.baseUrl).toBe(TEST_BASE_URL);
expect(config.clientId).toBe('client123');
});

it('should accept optional configurations', () => {
const config: AsgardeoBrowserConfig = {
baseUrl: TEST_BASE_URL,
clientId: 'client123',
signInRedirectURL: `${TEST_BASE_URL}/signin`,
signOutRedirectURL: `${TEST_BASE_URL}/signout`,
storage: 'sessionStorage',
};

// Test actual values instead of just checking if defined
expect(config.signInRedirectURL).toBe(`${TEST_BASE_URL}/signin`);
expect(config.signOutRedirectURL).toBe(`${TEST_BASE_URL}/signout`);
expect(config.storage).toBe('sessionStorage');
});
});

describe('Storage Type Validation', () => {
validStorageTypes.forEach((storageType: (typeof validStorageTypes)[number]) => {
it(`should accept ${storageType} as storage type`, () => {
const config: AsgardeoBrowserConfig = {
baseUrl: TEST_BASE_URL,
clientId: 'client123',
storage: storageType,
};
expect(config.storage).toBe(storageType);
});
});

it('should enforce valid storage types at compile time', () => {
const configs: AsgardeoBrowserConfig[] = validStorageTypes.map(
(storage: (typeof validStorageTypes)[number]) =>
({
baseUrl: TEST_BASE_URL,
clientId: 'client123',
storage,
}) as AsgardeoBrowserConfig,
);

configs.forEach((config: AsgardeoBrowserConfig) => {
expect(validStorageTypes).toContain(config.storage);
});
});
});

describe('Optional Configurations', () => {
it('should accept optional configurations', () => {
// Use type assertion to handle extended config properties
const config: Partial<AsgardeoBrowserConfig> = {
baseUrl: TEST_BASE_URL,
clientId: 'client123',
signInRedirectURL: `${TEST_BASE_URL}/signin`,
signOutRedirectURL: `${TEST_BASE_URL}/signout`,
storage: 'sessionStorage' as const,
} satisfies Partial<AsgardeoBrowserConfig>;

// Validate fields
expect(config.signInRedirectURL).toBe(`${TEST_BASE_URL}/signin`);
expect(config.signOutRedirectURL).toBe(`${TEST_BASE_URL}/signout`);
expect(config.storage).toBe('sessionStorage');
expect(validStorageTypes).toContain(config.storage);
});
});

// Remove the incomplete invalid storage test
// Add runtime validation test instead
it('should not accept invalid storage type at runtime', () => {
const invalidStorage: string = 'invalid' as any;
expect((validStorageTypes as readonly string[]).includes(invalidStorage)).toBe(false);
});
});
7 changes: 5 additions & 2 deletions packages/browser/src/models/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
* under the License.
*/

import {Config} from '@asgardeo/javascript';
import { Config } from '@asgardeo/javascript';

export type AsgardeoBrowserConfig = Config<'sessionStorage' | 'localStorage' | 'browserMemory' | 'webWorker'>;
export type AsgardeoBrowserConfig = Config<'sessionStorage' | 'localStorage' | 'browserMemory' | 'webWorker'> & {
signInRedirectURL?: string;
signOutRedirectURL?: string;
};
Loading