Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BA-2020: mobile internal testing #172

Merged
merged 1 commit into from
Jan 9, 2025
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
11 changes: 11 additions & 0 deletions packages/authentication/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# @baseapp-frontend/authentication

## 4.1.0

### Minor Changes

- Adapt `useLogin` to use `isMobilePlatform` to omit current profile logic from mobile platforms.
- Replace `process.env.EXPO_PUBLIC_API_BASE_URL` usage with `getExpoConstant`.
- Adapt `getUser` and `getUserAsync` tests to mock `getToken` and `getTokenAsync`.
- Add `expo` and `react-native` mock files.
- Updated dependencies
- @baseapp-frontend/utils@3.0.6
Comment on lines +3 to +12
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Mobile platform test coverage is incomplete

The mobile-specific changes in useLogin lack test coverage:

  • No tests verifying the conditional logic using isMobilePlatform
  • Missing test cases for mobile vs non-mobile behavior differences
  • Platform detection function is not mocked in tests
🔗 Analysis chain

Verify mobile platform test coverage

The changes introduce significant mobile-specific adaptations:

  1. Mobile-specific logic in useLogin
  2. Environment variable handling changes
  3. New mock files for expo and react-native
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check test coverage for mobile scenarios
echo "Checking mobile-specific test coverage..."
rg -B 2 -A 2 "isMobilePlatform" packages/authentication/

# Verify mock implementations
echo "Verifying mock files..."
fd -e ts -e js "mock" packages/authentication/ --exec cat {}

Length of output: 1978


Script:

#!/bin/bash
# Check useLogin tests for mobile coverage
echo "Checking useLogin tests..."
fd "useLogin" packages/authentication/ -t f | grep -i "test\|spec" | xargs cat

# Check if isMobilePlatform is mocked in tests
echo "Checking for isMobilePlatform mocks..."
rg -B 2 -A 2 "mock.*isMobilePlatform" packages/authentication/

Length of output: 3269


## 4.0.7

### Patch Changes
Expand Down
3 changes: 3 additions & 0 deletions packages/authentication/__mocks__/expo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = require('@baseapp-frontend/test/__mocks__/expo.ts')

export {}
3 changes: 0 additions & 3 deletions packages/authentication/__mocks__/expoSecureStoreMock.ts

This file was deleted.

3 changes: 3 additions & 0 deletions packages/authentication/__mocks__/react-native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = require('@baseapp-frontend/test/__mocks__/react-native.ts')

export {}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getExpoConstant } from '@baseapp-frontend/utils'
import type { JWTResponse } from '@baseapp-frontend/utils/types/jwt'

const preAuthenticateJWT = async (token?: string) => {
Expand All @@ -6,8 +7,9 @@ const preAuthenticateJWT = async (token?: string) => {
throw new Error('No token provided.')
}

const EXPO_PUBLIC_API_BASE_URL = getExpoConstant('EXPO_PUBLIC_API_BASE_URL')
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_BASE_URL ?? process.env.EXPO_PUBLIC_API_BASE_URL}/auth/pre-auth/jwt`,
`${process.env.NEXT_PUBLIC_API_BASE_URL ?? EXPO_PUBLIC_API_BASE_URL}/auth/pre-auth/jwt`,
Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extracting URL resolution to a shared utility.

This URL resolution pattern is duplicated across multiple files. Consider extracting it to a shared utility function.

+// In packages/utils/functions/url/index.ts
+export const getApiBaseUrl = () => {
+  const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? getExpoConstant('EXPO_PUBLIC_API_BASE_URL')
+  if (!baseUrl) {
+    throw new Error('API base URL is not configured.')
+  }
+  return baseUrl
+}

-    const EXPO_PUBLIC_API_BASE_URL = getExpoConstant('EXPO_PUBLIC_API_BASE_URL')
-    const response = await fetch(
-      `${process.env.NEXT_PUBLIC_API_BASE_URL ?? EXPO_PUBLIC_API_BASE_URL}/auth/pre-auth/jwt`,
+    const response = await fetch(
+      `${getApiBaseUrl()}/auth/pre-auth/jwt`,

Committable suggestion skipped: line range outside the PR's diff.

{
method: 'POST',
body: JSON.stringify({ token }),
Expand Down
27 changes: 17 additions & 10 deletions packages/authentication/modules/access/useLogin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
setFormApiErrors,
setTokenAsync,
} from '@baseapp-frontend/utils'
import { isMobilePlatform } from '@baseapp-frontend/utils/functions/os'

import { zodResolver } from '@hookform/resolvers/zod'
import { useMutation } from '@tanstack/react-query'
Expand Down Expand Up @@ -53,16 +54,21 @@ const useLogin = ({
if (isLoginChangeExpiredPasswordRedirectResponse(response)) {
return
}
const user = decodeJWT<User>(response.access)
if (user) {
// TODO: handle the absolute image path on the backend
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL?.replace('/v1', '')
const absoluteImagePath = user?.profile?.image ? `${baseUrl}${user.profile.image}` : null
setCurrentProfile({
...user.profile,
image: absoluteImagePath,
})

// TODO: adapt this flow to work with mobile
if (!isMobilePlatform()) {
const user = decodeJWT<User>(response.access)
if (user) {
// TODO: handle the absolute image path on the backend
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL?.replace('/v1', '')
const absoluteImagePath = user?.profile?.image ? `${baseUrl}${user.profile.image}` : null
setCurrentProfile({
...user.profile,
image: absoluteImagePath,
})
}
}

await setTokenAsync(accessKeyName, response.access, {
secure: process.env.NODE_ENV === 'production',
})
Expand Down Expand Up @@ -91,8 +97,9 @@ const useLogin = ({
if (isLoginMfaResponse(response)) {
setMfaEphemeralToken(response.ephemeralToken)
} else {
handleLoginSuccess(response)
await handleLoginSuccess(response)
}
// onSuccess should only run after we successfully await the handleLoginSuccess (that sets the auth tokens asynchonously)
loginOptions?.onSuccess?.(response, variables, context)
},
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import type { CookiesGetByNameFn } from '@baseapp-frontend/test'

import Cookies from 'js-cookie'
import { getToken } from '@baseapp-frontend/utils/functions/token/getToken'

import getUser from '../index'
import jwt from './fixtures/jwt.json'

jest.mock('@baseapp-frontend/utils/functions/token/getToken', () => ({
getToken: jest.fn(),
}))

describe('getUser', () => {
it('should return the user from the JWT cookie', async () => {
;(Cookies.get as CookiesGetByNameFn) = jest.fn(() => jwt.token)
it('should return the user from the JWT token', () => {
;(getToken as jest.Mock).mockReturnValue(jwt.token)
const user = getUser()

expect(user?.email).toBe('user@company.com')
expect(user?.firstName).toBe('John')
expect(user?.lastName).toBe('Doe')
})

it('should return null if the JWT cookie is not set', async () => {
;(Cookies.get as CookiesGetByNameFn) = jest.fn(() => undefined)
it('should return null if no token is set', () => {
;(getToken as jest.Mock).mockReturnValue(undefined)
const user = getUser()

expect(user).toBeNull()
})
})
3 changes: 2 additions & 1 deletion packages/authentication/modules/user/getUser/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ACCESS_KEY_NAME } from '@baseapp-frontend/utils/constants/jwt'
import { decodeJWT, getToken } from '@baseapp-frontend/utils/functions/token'
import { decodeJWT } from '@baseapp-frontend/utils/functions/token/decodeJWT'
import { getToken } from '@baseapp-frontend/utils/functions/token/getToken'
import type { JWTContent } from '@baseapp-frontend/utils/types/jwt'

import type { User } from '../../../types/user'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import type { CookiesGetByNameFn } from '@baseapp-frontend/test'

import Cookies from 'js-cookie'
import { getTokenAsync } from '@baseapp-frontend/utils/functions/token/getTokenAsync'

Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Transition to token-based authentication is incomplete

The codebase shows mixed authentication methods:

  • Token-based infrastructure is fully implemented
  • Several core components still use cookies for authentication:
    • packages/utils/functions/token/setTokenAsync/index.ts
    • packages/utils/functions/axios/createAxiosInstance/index.ts
    • packages/authentication/modules/user/useUser/index.ts

Note: Cookie usage for language preferences is a separate concern and can be retained.

🔗 Analysis chain

LGTM! Clean transition from cookies to tokens.

The import changes and mock setup correctly reflect the architectural shift from cookie-based to token-based authentication.

Let's verify that this transition is consistent across the codebase:

Also applies to: 6-8

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining cookie-based authentication
# that might have been missed during the transition

# Search for remaining js-cookie imports
echo "Checking for remaining js-cookie imports..."
rg "from 'js-cookie'" -l

# Search for cookie-related function calls
echo "Checking for remaining cookie-related function calls..."
rg "Cookies\.(get|set|remove)" -l

# Search for cookie-related types/interfaces
echo "Checking for cookie-related types..."
rg "CookiesGetByNameFn" -l

Length of output: 2214


Script:

#!/bin/bash
# Check the context of cookie usage in core functionality files
echo "Checking cookie usage in core functionality files..."
echo -e "\nsetTokenAsync implementation:"
rg -A 5 "from 'js-cookie'" packages/utils/functions/token/setTokenAsync/index.ts

echo -e "\naxios instance creation:"
rg -A 5 "from 'js-cookie'" packages/utils/functions/axios/createAxiosInstance/index.ts

echo -e "\nCookie usage in authentication modules:"
rg -A 5 "Cookies\.(get|set|remove)" packages/authentication/modules/user/useUser/index.ts

echo -e "\nLanguage-related cookie usage:"
rg -A 5 "Cookies\.(get|set|remove)" packages/utils/functions/language/getLanguage/__tests__/getLanguage.client.test.ts

Length of output: 1721


Script:

#!/bin/bash
# Check for token-based implementations
echo "Checking token-based implementations..."
echo -e "\nToken storage/retrieval implementations:"
rg -A 5 "getTokenAsync|setTokenAsync" packages/utils/functions/token/ -l

echo -e "\nToken usage in authentication:"
rg -A 5 "getTokenAsync|setTokenAsync" packages/authentication/modules/user/ -l

echo -e "\nCheck if there are parallel implementations:"
ast-grep --pattern 'function $_($$TOKEN$$) {
  $$$
  Cookies.$_($$$)
  $$$
}'

Length of output: 1532

import getUserAsync from '../index'
import jwt from './fixtures/jwt.json'

jest.mock('@baseapp-frontend/utils/functions/token/getTokenAsync', () => ({
getTokenAsync: jest.fn(),
}))

describe('getUserAsync', () => {
it('should return the user from the JWT cookie', async () => {
;(Cookies.get as CookiesGetByNameFn) = jest.fn(() => jwt.token)
it('should return the user from the JWT token', async () => {
;(getTokenAsync as jest.Mock).mockReturnValue(jwt.token)

const user = await getUserAsync()

expect(user?.email).toBe('user@company.com')
expect(user?.firstName).toBe('John')
expect(user?.lastName).toBe('Doe')
})

it('should return null if the JWT cookie is not set', async () => {
;(Cookies.get as CookiesGetByNameFn) = jest.fn(() => undefined)
it('should return null if no token is set', async () => {
;(getTokenAsync as jest.Mock).mockReturnValue(undefined)

const user = await getUserAsync()

expect(user).toBeNull()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ACCESS_KEY_NAME } from '@baseapp-frontend/utils/constants/jwt'
import { decodeJWT, getTokenAsync } from '@baseapp-frontend/utils/functions/token'
import { decodeJWT } from '@baseapp-frontend/utils/functions/token/decodeJWT'
import { getTokenAsync } from '@baseapp-frontend/utils/functions/token/getTokenAsync'
import type { JWTContent } from '@baseapp-frontend/utils/types/jwt'

import type { User } from '../../../types/user'
Expand Down
2 changes: 1 addition & 1 deletion packages/authentication/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@baseapp-frontend/authentication",
"description": "Authentication modules.",
"version": "4.0.7",
"version": "4.1.0",
"main": "./index.ts",
"types": "dist/index.d.ts",
"sideEffects": false,
Expand Down
11 changes: 11 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# @baseapp-frontend/components

## 0.0.44

### Patch Changes

- Add `expo` and `react-native` mock files.
- Updated dependencies
- @baseapp-frontend/authentication@4.0.7
- @baseapp-frontend/graphql@1.2.0
- @baseapp-frontend/utils@3.1.0
- @baseapp-frontend/design-system@0.0.28

## 0.0.43

### Patch Changes
Expand Down
3 changes: 3 additions & 0 deletions packages/components/__mocks__/expo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = require('@baseapp-frontend/test/__mocks__/expo.ts')

export {}
3 changes: 0 additions & 3 deletions packages/components/__mocks__/expoSecureStoreMock.ts

This file was deleted.

3 changes: 3 additions & 0 deletions packages/components/__mocks__/react-native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = require('@baseapp-frontend/test/__mocks__/react-native.ts')

export {}
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@baseapp-frontend/components",
"description": "BaseApp components modules such as comments, notifications, messages, and more.",
"version": "0.0.43",
"version": "0.0.44",
"main": "./index.ts",
"types": "dist/index.d.ts",
"sideEffects": false,
Expand Down
9 changes: 8 additions & 1 deletion packages/design-system/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# @baseapp-frontend/design-system

## 0.0.29

### Patch Changes

- Updated dependencies
- @baseapp-frontend/utils@3.1.0

## 0.0.28

### Major Changes
### Patch Changes

- MDX documentation files added for several components

Expand Down
8 changes: 8 additions & 0 deletions packages/graphql/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @baseapp-frontend/graphql

## 1.2.0

### Minor Changes

- Replace `process.env.EXPO_PUBLIC_API_BASE_URL` and `EXPO_PUBLIC_WS_RELAY_ENDPOINT` usage with `getExpoConstant`.
- Updated dependencies
- @baseapp-frontend/utils@3.1.0

## 1.1.15

### Patch Changes
Expand Down
10 changes: 6 additions & 4 deletions packages/graphql/config/environment.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getExpoConstant } from '@baseapp-frontend/utils'
import { baseAppFetch } from '@baseapp-frontend/utils/functions/fetch/baseAppFetch'
import { getToken } from '@baseapp-frontend/utils/functions/token/getToken'

Expand Down Expand Up @@ -64,9 +65,10 @@ export async function httpFetch(
uploadables?: UploadableMap | null,
): Promise<GraphQLResponse> {
const fetchOptions = getFetchOptions({ request, variables, uploadables })
const EXPO_PUBLIC_RELAY_ENDPOINT = getExpoConstant('EXPO_PUBLIC_RELAY_ENDPOINT')

const response = await baseAppFetch('', {
baseUrl: (process.env.NEXT_PUBLIC_RELAY_ENDPOINT ||
process.env.EXPO_PUBLIC_RELAY_ENDPOINT) as string,
baseUrl: (process.env.NEXT_PUBLIC_RELAY_ENDPOINT ?? EXPO_PUBLIC_RELAY_ENDPOINT) as string,
decamelizeRequestBodyKeys: false,
decamelizeRequestParamsKeys: false,
camelizeResponseDataKeys: false,
Expand All @@ -89,9 +91,9 @@ export async function httpFetch(
return response
}

const EXPO_PUBLIC_WS_RELAY_ENDPOINT = getExpoConstant('EXPO_PUBLIC_WS_RELAY_ENDPOINT')
const wsClient = createClient({
url: (process.env.NEXT_PUBLIC_WS_RELAY_ENDPOINT ||
process.env.EXPO_PUBLIC_WS_RELAY_ENDPOINT) as string,
url: (process.env.NEXT_PUBLIC_WS_RELAY_ENDPOINT ?? EXPO_PUBLIC_WS_RELAY_ENDPOINT) as string,
connectionParams: () => {
const Authorization = getToken()
if (!Authorization) return {}
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@baseapp-frontend/graphql",
"description": "GraphQL configurations and utilities",
"version": "1.1.15",
"version": "1.2.0",
"main": "./index.ts",
"types": "dist/index.d.ts",
"sideEffects": false,
Expand Down
5 changes: 1 addition & 4 deletions packages/graphql/utils/createTestEnvironment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,8 @@
queryName,
}: QueueOperationResolverParams) => {
try {
console.log('queueOperationResolver', mockResolvers, data, queryName)
environment.mock.queueOperationResolver((operationToResolve) => {
console.log('AOPS', operationToResolve)
if (mockResolvers) {
console.log('mockResolvers', mockResolvers)

return MockPayloadGenerator.generate(operationToResolve, mockResolvers)
}

Expand All @@ -55,7 +51,8 @@
return data
}

console.warn('The operation was not mocked.')

Check warning on line 54 in packages/graphql/utils/createTestEnvironment/index.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement

return null
})
} catch (e) {
Expand Down
7 changes: 7 additions & 0 deletions packages/provider/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @baseapp-frontend/provider

## 2.0.7

### Patch Changes

- Updated dependencies
- @baseapp-frontend/utils@3.1.0

## 2.0.6

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/provider/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@baseapp-frontend/provider",
"description": "Providers for React Query and Emotion.",
"version": "2.0.6",
"version": "2.0.7",
"main": "./index.ts",
"types": "dist/index.d.ts",
"sideEffects": false,
Expand Down
6 changes: 6 additions & 0 deletions packages/test/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @baseapp-frontend/test

## 2.0.6

### Patch Changes

- Add `expo` and `react-native` mock files.

## 2.0.5

### Patch Changes
Expand Down
26 changes: 26 additions & 0 deletions packages/test/__mocks__/expo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const ExpoSecureStore = {
getItemAsync: jest.fn(async (key: string) => {
return key === 'ACCESS_KEY_NAME' ? 'mocked_value' : null
}),
setItemAsync: jest.fn(async (key: string, value: string) => {
return true
}),
deleteItemAsync: jest.fn(async (key: string) => {
return true
}),
}

const Constants = {
expoConfig: {
extra: {
EXPO_PUBLIC_API_BASE_URL: undefined,
EXPO_PUBLIC_RELAY_ENDPOINT: undefined,
EXPO_PUBLIC_WS_RELAY_ENDPOINT: undefined,
},
},
}

module.exports = {
...ExpoSecureStore,
...Constants,
}
13 changes: 0 additions & 13 deletions packages/test/__mocks__/expoSecureStoreMock.ts

This file was deleted.

4 changes: 4 additions & 0 deletions packages/test/__mocks__/react-native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// ignoring react-native imports in tests, we may change this in the future
module.exports = {
Platform: { OS: 'web' },
}
Loading
Loading