Skip to content

Commit 59fda2d

Browse files
Merge pull request #74 from stytchauth/jordan/fix-sbom
Fix SBOM publishing (& yarn build apparently)
2 parents c9a6751 + 4996e62 commit 59fda2d

7 files changed

Lines changed: 149 additions & 72 deletions

File tree

.github/workflows/_generate-sbom.yml

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
name: Generate SBOM (reusable)
22

33
on:
4-
workflow_dispatch:
54
workflow_call:
65

76
jobs:
@@ -31,10 +30,7 @@ jobs:
3130

3231
- name: Publish shared metadata to mavenLocal
3332
working-directory: source/shared
34-
run: >
35-
./gradlew
36-
:sdk:shared:publishKotlinMultiplatformPublicationToMavenLocal
37-
:sdk:shared:publishJvmPublicationToMavenLocal
33+
run: ./gradlew :sdk:shared:publishToMavenLocal
3834

3935
- name: Generate consumer and B2B SDK SBOMs
4036
working-directory: source/sdks
@@ -43,14 +39,6 @@ jobs:
4339
:sdk:consumer-headless:cyclonedxBom
4440
:sdk:b2b-headless:cyclonedxBom
4541
46-
- name: Resolve SPM dependencies
47-
working-directory: source/ios
48-
run: swift package resolve
49-
50-
- name: Generate iOS SPM SBOM
51-
working-directory: source/ios
52-
run: swift package show-dependencies --format json > sbom-ios-spm.json
53-
5442
- name: Install consumer RN dependencies
5543
working-directory: source/react-native/consumer
5644
run: yarn install --frozen-lockfile
@@ -73,7 +61,6 @@ jobs:
7361
cp source/shared/sdk/shared/build/reports/bom.json sbom-artifacts/sbom-shared.json
7462
cp source/sdks/sdk/consumer-headless/build/reports/bom.json sbom-artifacts/sbom-consumer.json
7563
cp source/sdks/sdk/b2b-headless/build/reports/bom.json sbom-artifacts/sbom-b2b.json
76-
cp source/ios/sbom-ios-spm.json sbom-artifacts/sbom-ios-spm.json
7764
cp source/react-native/consumer/sbom-rn-consumer.json sbom-artifacts/sbom-rn-consumer.json
7865
cp source/react-native/b2b/sbom-rn-b2b.json sbom-artifacts/sbom-rn-b2b.json
7966

.github/workflows/publish.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,23 @@ jobs:
139139
working-directory: source/sdks
140140
run: ./gradlew assembleStytchConsumerSDKXCFramework assembleStytchB2BSDKXCFramework
141141

142+
- name: Copy xcframeworks to source/ios for SPM resolution
143+
run: |
144+
cp -r source/sdks/sdk/consumer-headless/build/XCFrameworks/release/StytchConsumerSDK.xcframework source/ios/
145+
cp -r source/sdks/sdk/b2b-headless/build/XCFrameworks/release/StytchB2BSDK.xcframework source/ios/
146+
cp -r source/shared/sdk/shared/build/XCFrameworks/release/StytchSharedSDK.xcframework source/ios/
147+
cp -r source/shared/sdk/shared/src/iosMain/interop/StytchSwiftUtils.xcframework source/ios/
148+
149+
- name: Generate iOS SPM SBOM
150+
working-directory: source/ios
151+
run: swift package show-dependencies --format json > sbom-ios-spm.json
152+
153+
- name: Upload iOS SPM SBOM
154+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
155+
with:
156+
name: sbom-ios-spm
157+
path: source/ios/sbom-ios-spm.json
158+
142159
- name: Zip xcframeworks
143160
run: |
144161
ARTIFACTS_DIR=$GITHUB_WORKSPACE/xcframework-artifacts
@@ -385,6 +402,12 @@ jobs:
385402
name: sbom
386403
path: /tmp/sbom/
387404

405+
- name: Download iOS SPM SBOM
406+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
407+
with:
408+
name: sbom-ios-spm
409+
path: /tmp/sbom/
410+
388411
- name: Create GitHub release
389412
if: env.DRY_RUN != 'true'
390413
env:

source/react-native/b2b/src/__tests__/providers.test.tsx

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
22
import React from 'react';
33
import { act, render, renderHook } from '@testing-library/react-native';
44
import { Text } from 'react-native';
5-
import { AppState } from 'react-native';
5+
import { AppState, AppStateStatus } from 'react-native';
66
import { StytchB2BProvider } from '../providers';
77
import {
88
useStytchB2B,
@@ -13,8 +13,11 @@ import {
1313
import {
1414
ApiB2bSessionV1MemberSession,
1515
ApiOrganizationV1Member,
16+
ApiOrganizationV1Organization,
1617
B2BAuthenticationState,
1718
StytchB2B,
19+
B2BSessionsAuthenticateParameters,
20+
B2BSessionsAuthenticateResponse,
1821
} from '../../lib/b2b-headless.mjs';
1922

2023
// ---------------------------------------------------------------------------
@@ -28,8 +31,12 @@ type AnyState =
2831
type ObserverCallback = (state: AnyState) => void | Promise<void>;
2932

3033
interface MockStytchB2BClient {
31-
authenticationStateObserver: jest.Mock;
32-
session: { authenticate: jest.Mock };
34+
authenticationStateObserver: jest.Mock<(params: ObserverCallback) => AnyState>;
35+
session: {
36+
authenticate: jest.Mock<
37+
(params: B2BSessionsAuthenticateParameters) => Promise<B2BSessionsAuthenticateResponse>
38+
>;
39+
};
3340
}
3441

3542
/**
@@ -52,7 +59,9 @@ function makeStytchMock() {
5259
return { stop };
5360
}),
5461
session: {
55-
authenticate: jest.fn().mockResolvedValue({}),
62+
authenticate: jest
63+
.fn<() => Promise<B2BSessionsAuthenticateResponse>>()
64+
.mockResolvedValue({} as B2BSessionsAuthenticateResponse),
5665
},
5766
};
5867

@@ -70,24 +79,27 @@ function makeStytchMock() {
7079
};
7180
}
7281

82+
const mockMember = {} as unknown as ApiOrganizationV1Member;
83+
const mockMemberSession = {} as unknown as ApiB2bSessionV1MemberSession;
84+
const mockOrganization = {} as unknown as ApiOrganizationV1Organization;
7385
function makeAuthenticatedState(
74-
member: ApiOrganizationV1Member = new ApiOrganizationV1Member(),
75-
memberSession: ApiB2bSessionV1MemberSession = new ApiB2bSessionV1MemberSession(),
86+
member: ApiOrganizationV1Member = mockMember,
87+
memberSession: ApiB2bSessionV1MemberSession = mockMemberSession,
7688
) {
77-
return Object.assign(new B2BAuthenticationState.Authenticated(), { member, memberSession });
89+
return new B2BAuthenticationState.Authenticated(member, memberSession, mockOrganization, '', '');
7890
}
7991

8092
// ---------------------------------------------------------------------------
8193
// AppState spy — lets tests trigger app-active events
8294
// ---------------------------------------------------------------------------
8395

84-
let capturedAppStateHandler: ((status: string) => void) | null = null;
96+
let capturedAppStateHandler: ((status: AppStateStatus) => void) | null = null;
8597
const appStateRemove = jest.fn();
8698

8799
beforeEach(() => {
88100
capturedAppStateHandler = null;
89101
appStateRemove.mockClear();
90-
jest.spyOn(AppState, 'addEventListener').mockImplementation((event: string, handler: (status: string) => void) => {
102+
jest.spyOn(AppState, 'addEventListener').mockImplementation((event, handler) => {
91103
if (event === 'change') capturedAppStateHandler = handler;
92104
return { remove: appStateRemove } as ReturnType<typeof AppState.addEventListener>;
93105
});
@@ -100,46 +112,64 @@ beforeEach(() => {
100112
describe('StytchB2BProvider', () => {
101113
it('subscribes to authenticationStateObserver on mount', () => {
102114
const { mockClient } = makeStytchMock();
103-
render(<StytchB2BProvider stytch={mockClient}><Text /></StytchB2BProvider>);
115+
render(
116+
<StytchB2BProvider stytch={mockClient}>
117+
<Text />
118+
</StytchB2BProvider>,
119+
);
104120
expect(mockClient.authenticationStateObserver).toHaveBeenCalled();
105121
});
106122

107123
it('stops the observer subscription on unmount', () => {
108124
const { mockClient, observerStops } = makeStytchMock();
109-
const { unmount } = render(<StytchB2BProvider stytch={mockClient}><Text /></StytchB2BProvider>);
125+
const { unmount } = render(
126+
<StytchB2BProvider stytch={mockClient}>
127+
<Text />
128+
</StytchB2BProvider>,
129+
);
110130
unmount();
111-
expect(observerStops.every(stop => stop.mock.calls.length > 0)).toBe(true);
131+
expect(observerStops.every((stop) => stop.mock.calls.length > 0)).toBe(true);
112132
});
113133

114134
it('removes the AppState listener on unmount', () => {
115135
const { mockClient } = makeStytchMock();
116-
const { unmount } = render(<StytchB2BProvider stytch={mockClient}><Text /></StytchB2BProvider>);
136+
const { unmount } = render(
137+
<StytchB2BProvider stytch={mockClient}>
138+
<Text />
139+
</StytchB2BProvider>,
140+
);
117141
unmount();
118142
expect(appStateRemove).toHaveBeenCalled();
119143
});
120144

121145
it('populates member and memberSession context when Authenticated state fires', async () => {
122146
const { mockClient, emitState } = makeStytchMock();
123-
const mockMember = new ApiOrganizationV1Member();
124-
const mockSession = new ApiB2bSessionV1MemberSession();
125147

126148
const { result } = renderHook(
127149
() => ({ member: useStytchMember(), memberSession: useStytchMemberSession() }),
128-
{ wrapper: ({ children }) => <StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider> },
150+
{
151+
wrapper: ({ children }) => (
152+
<StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>
153+
),
154+
},
129155
);
130156

131-
await emitState(makeAuthenticatedState(mockMember, mockSession));
157+
await emitState(makeAuthenticatedState(mockMember, mockMemberSession));
132158

133159
expect(result.current.member).toBe(mockMember);
134-
expect(result.current.memberSession).toBe(mockSession);
160+
expect(result.current.memberSession).toBe(mockMemberSession);
135161
});
136162

137163
it('clears member and memberSession context when Unauthenticated state fires', async () => {
138164
const { mockClient, emitState } = makeStytchMock();
139165

140166
const { result } = renderHook(
141167
() => ({ member: useStytchMember(), memberSession: useStytchMemberSession() }),
142-
{ wrapper: ({ children }) => <StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider> },
168+
{
169+
wrapper: ({ children }) => (
170+
<StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>
171+
),
172+
},
143173
);
144174

145175
await emitState(makeAuthenticatedState());
@@ -154,10 +184,11 @@ describe('StytchB2BProvider', () => {
154184
const { mockClient, emitState } = makeStytchMock();
155185
const authenticatedState = makeAuthenticatedState();
156186

157-
const { result } = renderHook(
158-
() => useStytchB2BAuthenticationState(),
159-
{ wrapper: ({ children }) => <StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider> },
160-
);
187+
const { result } = renderHook(() => useStytchB2BAuthenticationState(), {
188+
wrapper: ({ children }) => (
189+
<StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>
190+
),
191+
});
161192

162193
expect(result.current).toBeInstanceOf(B2BAuthenticationState.Loading);
163194
await emitState(authenticatedState);
@@ -167,7 +198,11 @@ describe('StytchB2BProvider', () => {
167198
it('calls session.authenticate when app becomes active and session is Authenticated', async () => {
168199
const { mockClient, emitState } = makeStytchMock();
169200

170-
render(<StytchB2BProvider stytch={mockClient}><Text /></StytchB2BProvider>);
201+
render(
202+
<StytchB2BProvider stytch={mockClient}>
203+
<Text />
204+
</StytchB2BProvider>,
205+
);
171206

172207
await act(async () => {
173208
capturedAppStateHandler!('active');
@@ -182,7 +217,11 @@ describe('StytchB2BProvider', () => {
182217
const { mockClient, emitState } = makeStytchMock();
183218
mockClient.session.authenticate.mockRejectedValueOnce(new Error('network error'));
184219

185-
render(<StytchB2BProvider stytch={mockClient}><Text /></StytchB2BProvider>);
220+
render(
221+
<StytchB2BProvider stytch={mockClient}>
222+
<Text />
223+
</StytchB2BProvider>,
224+
);
186225

187226
await act(async () => {
188227
capturedAppStateHandler!('active');
@@ -195,7 +234,11 @@ describe('StytchB2BProvider', () => {
195234
it('does not call session.authenticate when app becomes active but state is not Authenticated', async () => {
196235
const { mockClient, emitState } = makeStytchMock();
197236

198-
render(<StytchB2BProvider stytch={mockClient}><Text /></StytchB2BProvider>);
237+
render(
238+
<StytchB2BProvider stytch={mockClient}>
239+
<Text />
240+
</StytchB2BProvider>,
241+
);
199242

200243
await act(async () => {
201244
capturedAppStateHandler!('active');
@@ -219,7 +262,9 @@ describe('withStytchB2B', () => {
219262
it('makes the stytch client available via useStytchB2B inside the provider', () => {
220263
const { mockClient } = makeStytchMock();
221264
const { result } = renderHook(() => useStytchB2B(), {
222-
wrapper: ({ children }) => <StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>,
265+
wrapper: ({ children }) => (
266+
<StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>
267+
),
223268
});
224269
expect(result.current).toBe(mockClient);
225270
});
@@ -229,16 +274,19 @@ describe('withStytchMember', () => {
229274
it('returns undefined when no member is in context', () => {
230275
const { mockClient } = makeStytchMock();
231276
const { result } = renderHook(() => useStytchMember(), {
232-
wrapper: ({ children }) => <StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>,
277+
wrapper: ({ children }) => (
278+
<StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>
279+
),
233280
});
234281
expect(result.current).toBeUndefined();
235282
});
236283

237284
it('reflects the member once Authenticated state fires', async () => {
238285
const { mockClient, emitState } = makeStytchMock();
239-
const mockMember = new ApiOrganizationV1Member();
240286
const { result } = renderHook(() => useStytchMember(), {
241-
wrapper: ({ children }) => <StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>,
287+
wrapper: ({ children }) => (
288+
<StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>
289+
),
242290
});
243291
await emitState(makeAuthenticatedState(mockMember));
244292
expect(result.current).toBe(mockMember);
@@ -249,7 +297,9 @@ describe('withStytchMemberSession', () => {
249297
it('returns undefined when no session is in context', () => {
250298
const { mockClient } = makeStytchMock();
251299
const { result } = renderHook(() => useStytchMemberSession(), {
252-
wrapper: ({ children }) => <StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>,
300+
wrapper: ({ children }) => (
301+
<StytchB2BProvider stytch={mockClient}>{children}</StytchB2BProvider>
302+
),
253303
});
254304
expect(result.current).toBeUndefined();
255305
});

0 commit comments

Comments
 (0)