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
+
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