@@ -2,71 +2,137 @@ package gitlab
22
33import (
44 "context"
5+ "encoding/json"
56 "net/http"
67 "net/http/httptest"
8+ "net/url"
9+ "strings"
710 "testing"
811)
912
10- func TestNormalizeBaseURL (t * testing.T ) {
11- tests := []struct {
12- name string
13- in string
14- want string
15- }{
16- {name : "default" , in : "" , want : DefaultBaseURL },
17- {name : "plain host" , in : "gitlab.example.com" , want : "https://gitlab.example.com" },
18- {name : "trim trailing slash" , in : "https://gitlab.example.com/" , want : "https://gitlab.example.com" },
13+ func TestAuthClientGenerateAuthURLIncludesPKCE (t * testing.T ) {
14+ client := NewAuthClient (nil )
15+ pkce , err := GeneratePKCECodes ()
16+ if err != nil {
17+ t .Fatalf ("GeneratePKCECodes() error = %v" , err )
1918 }
2019
21- for _ , tc := range tests {
22- t .Run (tc .name , func (t * testing.T ) {
23- if got := NormalizeBaseURL (tc .in ); got != tc .want {
24- t .Fatalf ("NormalizeBaseURL(%q) = %q, want %q" , tc .in , got , tc .want )
25- }
26- })
20+ rawURL , err := client .GenerateAuthURL ("https://gitlab.example.com" , "client-id" , RedirectURL (17171 ), "state-123" , pkce )
21+ if err != nil {
22+ t .Fatalf ("GenerateAuthURL() error = %v" , err )
23+ }
24+
25+ parsed , err := url .Parse (rawURL )
26+ if err != nil {
27+ t .Fatalf ("Parse(authURL) error = %v" , err )
28+ }
29+ if got := parsed .Path ; got != "/oauth/authorize" {
30+ t .Fatalf ("expected /oauth/authorize path, got %q" , got )
31+ }
32+ query := parsed .Query ()
33+ if got := query .Get ("client_id" ); got != "client-id" {
34+ t .Fatalf ("expected client_id, got %q" , got )
35+ }
36+ if got := query .Get ("scope" ); got != defaultOAuthScope {
37+ t .Fatalf ("expected scope %q, got %q" , defaultOAuthScope , got )
38+ }
39+ if got := query .Get ("code_challenge_method" ); got != "S256" {
40+ t .Fatalf ("expected PKCE method S256, got %q" , got )
41+ }
42+ if got := query .Get ("code_challenge" ); got == "" {
43+ t .Fatal ("expected non-empty code_challenge" )
2744 }
2845}
2946
30- func TestFetchDirectAccess_ParsesModelDetails (t * testing.T ) {
31- server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
32- if r .Method != http . MethodPost {
33- t .Fatalf ("expected POST, got %s " , r .Method )
47+ func TestAuthClientExchangeCodeForTokens (t * testing.T ) {
48+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
49+ if r .URL . Path != "/oauth/token" {
50+ t .Fatalf ("unexpected path %q " , r .URL . Path )
3451 }
35- if got := r .Header . Get ( "Authorization" ); got != "Bearer pat-123" {
36- t .Fatalf ("expected Authorization header, got %q " , got )
52+ if err := r .ParseForm ( ); err != nil {
53+ t .Fatalf ("ParseForm() error = %v " , err )
3754 }
38- w . Header (). Set ( "Content-Type" , "application/json" )
39- _ , _ = w . Write ([] byte ( `{
40- "base_url":"https://gateway.gitlab.example.com/v1",
41- "token":"duo-gateway-token",
42- "expires_at":2000000000,
43- "headers":{
44- "X-Gitlab-Realm":"saas",
45- "X-Gitlab-Host-Name":"gitlab.example.com"
46- } ,
47- "model_details":{
48- "model_provider":"anthropic ",
49- "model_name":"claude-sonnet-4-5"
50- }
51- }` ) )
55+ if got := r . Form . Get ( "grant_type" ); got != "authorization_code" {
56+ t . Fatalf ( "expected authorization_code grant, got %q" , got )
57+ }
58+ if got := r . Form . Get ( "code_verifier" ); got != "verifier-123" {
59+ t . Fatalf ( "expected code_verifier, got %q" , got )
60+ }
61+ _ = json . NewEncoder ( w ). Encode ( map [ string ] any {
62+ "access_token" : "oauth-access" ,
63+ "refresh_token" : "oauth-refresh" ,
64+ "token_type" : "Bearer" ,
65+ "scope" : "api read_user " ,
66+ "created_at" : 1710000000 ,
67+ "expires_in" : 3600 ,
68+ })
5269 }))
53- defer server .Close ()
70+ defer srv .Close ()
5471
55- client := & AuthClient { httpClient : server . Client ()}
56- direct , err := client .FetchDirectAccess (context .Background (), server .URL , "pat -123" )
72+ client := NewAuthClient ( nil )
73+ token , err := client .ExchangeCodeForTokens (context .Background (), srv .URL , "client-id" , "client-secret" , RedirectURL ( 17171 ), "auth-code" , "verifier -123" )
5774 if err != nil {
58- t .Fatalf ("FetchDirectAccess returned error: %v" , err )
75+ t .Fatalf ("ExchangeCodeForTokens() error = %v" , err )
5976 }
60- if direct . BaseURL != "https://gateway.gitlab.example.com/v1 " {
61- t .Fatalf ("unexpected base_url %q" , direct . BaseURL )
77+ if token . AccessToken != "oauth-access " {
78+ t .Fatalf ("expected access token, got %q" , token . AccessToken )
6279 }
63- if direct . Token != "duo-gateway-token " {
64- t .Fatalf ("unexpected token %q" , direct . Token )
80+ if token . RefreshToken != "oauth-refresh " {
81+ t .Fatalf ("expected refresh token, got %q" , token . RefreshToken )
6582 }
66- if direct .ModelDetails == nil || direct .ModelDetails .ModelName != "claude-sonnet-4-5" {
67- t .Fatalf ("unexpected model details: %+v" , direct .ModelDetails )
83+ }
84+
85+ func TestExtractDiscoveredModels (t * testing.T ) {
86+ models := ExtractDiscoveredModels (map [string ]any {
87+ "model_details" : map [string ]any {
88+ "model_provider" : "anthropic" ,
89+ "model_name" : "claude-sonnet-4-5" ,
90+ },
91+ "supported_models" : []any {
92+ map [string ]any {"model_provider" : "openai" , "model_name" : "gpt-4.1" },
93+ "claude-sonnet-4-5" ,
94+ },
95+ })
96+ if len (models ) != 2 {
97+ t .Fatalf ("expected 2 unique models, got %d" , len (models ))
98+ }
99+ if models [0 ].ModelName != "claude-sonnet-4-5" {
100+ t .Fatalf ("unexpected first model %q" , models [0 ].ModelName )
101+ }
102+ if models [1 ].ModelName != "gpt-4.1" {
103+ t .Fatalf ("unexpected second model %q" , models [1 ].ModelName )
104+ }
105+ }
106+
107+ func TestFetchDirectAccessDecodesModelDetails (t * testing.T ) {
108+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
109+ if r .URL .Path != "/api/v4/code_suggestions/direct_access" {
110+ t .Fatalf ("unexpected path %q" , r .URL .Path )
111+ }
112+ if got := r .Header .Get ("Authorization" ); ! strings .Contains (got , "token-123" ) {
113+ t .Fatalf ("expected bearer token, got %q" , got )
114+ }
115+ _ = json .NewEncoder (w ).Encode (map [string ]any {
116+ "base_url" : "https://cloud.gitlab.example.com" ,
117+ "token" : "gateway-token" ,
118+ "expires_at" : 1710003600 ,
119+ "headers" : map [string ]string {
120+ "X-Gitlab-Realm" : "saas" ,
121+ },
122+ "model_details" : map [string ]any {
123+ "model_provider" : "anthropic" ,
124+ "model_name" : "claude-sonnet-4-5" ,
125+ },
126+ })
127+ }))
128+ defer srv .Close ()
129+
130+ client := NewAuthClient (nil )
131+ direct , err := client .FetchDirectAccess (context .Background (), srv .URL , "token-123" )
132+ if err != nil {
133+ t .Fatalf ("FetchDirectAccess() error = %v" , err )
68134 }
69- if direct .Headers [ "X-Gitlab-Realm" ] != "saas " {
70- t .Fatalf ("expected X-Gitlab-Realm header , got %+v" , direct .Headers )
135+ if direct .ModelDetails == nil || direct . ModelDetails . ModelName != "claude-sonnet-4-5 " {
136+ t .Fatalf ("expected model details , got %+v" , direct .ModelDetails )
71137 }
72138}
0 commit comments