1+ import { OAuthRegisteredClientsStore } from './clients.js' ;
2+ import { OAuthClientInformationFull } from '../../shared/auth.js' ;
3+
4+ describe ( 'OAuthRegisteredClientsStore' , ( ) => {
5+ // Create a mock implementation class for testing
6+ class MockClientStore implements OAuthRegisteredClientsStore {
7+ private clients : Record < string , OAuthClientInformationFull > = { } ;
8+
9+ async getClient ( clientId : string ) : Promise < OAuthClientInformationFull | undefined > {
10+ const client = this . clients [ clientId ] ;
11+
12+ // Return undefined for non-existent client
13+ if ( ! client ) return undefined ;
14+
15+ // Check if client secret has expired
16+ if ( client . client_secret &&
17+ client . client_secret_expires_at &&
18+ client . client_secret_expires_at < Math . floor ( Date . now ( ) / 1000 ) ) {
19+ // If expired, retain client but remove the secret
20+ const { client_secret : _unused , ...clientWithoutSecret } = client ;
21+ return clientWithoutSecret as OAuthClientInformationFull ;
22+ }
23+
24+ return client ;
25+ }
26+
27+ async registerClient ( client : OAuthClientInformationFull ) : Promise < OAuthClientInformationFull > {
28+ this . clients [ client . client_id ] = { ...client } ;
29+ return client ;
30+ }
31+ }
32+
33+ let mockStore : MockClientStore ;
34+
35+ beforeEach ( ( ) => {
36+ mockStore = new MockClientStore ( ) ;
37+ } ) ;
38+
39+ describe ( 'getClient' , ( ) => {
40+ it ( 'returns undefined for non-existent client' , async ( ) => {
41+ const result = await mockStore . getClient ( 'non-existent-id' ) ;
42+ expect ( result ) . toBeUndefined ( ) ;
43+ } ) ;
44+
45+ it ( 'returns client information for existing client' , async ( ) => {
46+ const mockClient : OAuthClientInformationFull = {
47+ client_id : 'test-client-123' ,
48+ client_secret : 'secret456' ,
49+ redirect_uris : [ 'https://example.com/callback' ]
50+ } ;
51+
52+ await mockStore . registerClient ( mockClient ) ;
53+ const result = await mockStore . getClient ( 'test-client-123' ) ;
54+
55+ expect ( result ) . toEqual ( mockClient ) ;
56+ } ) ;
57+
58+ it ( 'handles expired client secrets correctly' , async ( ) => {
59+ const now = Math . floor ( Date . now ( ) / 1000 ) ;
60+
61+ // Client with expired secret (one hour in the past)
62+ const expiredClient : OAuthClientInformationFull = {
63+ client_id : 'expired-client' ,
64+ client_secret : 'expired-secret' ,
65+ client_secret_expires_at : now - 3600 ,
66+ redirect_uris : [ 'https://example.com/callback' ]
67+ } ;
68+
69+ await mockStore . registerClient ( expiredClient ) ;
70+ const result = await mockStore . getClient ( 'expired-client' ) ;
71+
72+ // Expect client to be returned but without the secret
73+ expect ( result ) . toBeDefined ( ) ;
74+ expect ( result ! . client_id ) . toBe ( 'expired-client' ) ;
75+ expect ( result ! . client_secret ) . toBeUndefined ( ) ;
76+ } ) ;
77+
78+ it ( 'keeps valid client secrets' , async ( ) => {
79+ const now = Math . floor ( Date . now ( ) / 1000 ) ;
80+
81+ // Client with valid secret (expires one hour in the future)
82+ const validClient : OAuthClientInformationFull = {
83+ client_id : 'valid-client' ,
84+ client_secret : 'valid-secret' ,
85+ client_secret_expires_at : now + 3600 ,
86+ redirect_uris : [ 'https://example.com/callback' ]
87+ } ;
88+
89+ await mockStore . registerClient ( validClient ) ;
90+ const result = await mockStore . getClient ( 'valid-client' ) ;
91+
92+ // Secret should still be present
93+ expect ( result ?. client_secret ) . toBe ( 'valid-secret' ) ;
94+ } ) ;
95+ } ) ;
96+
97+ describe ( 'registerClient' , ( ) => {
98+ it ( 'successfully registers a new client' , async ( ) => {
99+ const newClient : OAuthClientInformationFull = {
100+ client_id : 'new-client-id' ,
101+ client_secret : 'new-client-secret' ,
102+ redirect_uris : [ 'https://example.com/callback' ]
103+ } ;
104+
105+ const result = await mockStore . registerClient ( newClient ) ;
106+
107+ // Verify registration returns the client
108+ expect ( result ) . toEqual ( newClient ) ;
109+
110+ // Verify the client is retrievable
111+ const storedClient = await mockStore . getClient ( 'new-client-id' ) ;
112+ expect ( storedClient ) . toEqual ( newClient ) ;
113+ } ) ;
114+
115+ it ( 'handles clients with all metadata fields' , async ( ) => {
116+ const fullClient : OAuthClientInformationFull = {
117+ client_id : 'full-client' ,
118+ client_secret : 'full-secret' ,
119+ client_id_issued_at : Math . floor ( Date . now ( ) / 1000 ) ,
120+ client_secret_expires_at : Math . floor ( Date . now ( ) / 1000 ) + 86400 , // 24 hours
121+ redirect_uris : [ 'https://example.com/callback' ] ,
122+ token_endpoint_auth_method : 'client_secret_basic' ,
123+ grant_types : [ 'authorization_code' , 'refresh_token' ] ,
124+ response_types : [ 'code' ] ,
125+ client_name : 'Test Client' ,
126+ client_uri : 'https://example.com' ,
127+ logo_uri : 'https://example.com/logo.png' ,
128+ scope : 'profile email' ,
129+ contacts : [ 'dev@example.com' ] ,
130+ tos_uri : 'https://example.com/tos' ,
131+ policy_uri : 'https://example.com/privacy' ,
132+ jwks_uri : 'https://example.com/jwks' ,
133+ software_id : 'test-software' ,
134+ software_version : '1.0.0'
135+ } ;
136+
137+ await mockStore . registerClient ( fullClient ) ;
138+ const result = await mockStore . getClient ( 'full-client' ) ;
139+
140+ expect ( result ) . toEqual ( fullClient ) ;
141+ } ) ;
142+ } ) ;
143+ } ) ;
0 commit comments