@@ -4,6 +4,7 @@ import { describe, it, expect, vi, beforeAll } from "vitest";
4
4
import * as vscode from "vscode" ;
5
5
import { Commands } from "./commands" ;
6
6
import { Storage } from "./storage" ;
7
+ import { createMockOutputChannelWithLogger } from "./test-helpers" ;
7
8
import { OpenableTreeItem } from "./workspacesProvider" ;
8
9
9
10
// Mock dependencies
@@ -13,6 +14,14 @@ vi.mock("./error");
13
14
vi . mock ( "./storage" ) ;
14
15
vi . mock ( "./util" ) ;
15
16
vi . mock ( "./workspacesProvider" ) ;
17
+ vi . mock ( "coder/site/src/api/errors" , ( ) => ( {
18
+ getErrorMessage :vi . fn ( ( error :unknown , defaultMessage :string ) => {
19
+ if ( error instanceof Error ) {
20
+ return error . message ;
21
+ }
22
+ return defaultMessage ;
23
+ } ) ,
24
+ } ) ) ;
16
25
17
26
beforeAll ( ( ) => {
18
27
vi . mock ( "vscode" , ( ) => {
@@ -1049,4 +1058,219 @@ describe("commands", () => {
1049
1058
) ;
1050
1059
} ) ;
1051
1060
} ) ;
1061
+
1062
+ describe ( "Logger integration" , ( ) => {
1063
+ it ( "should log autologin failure messages through Logger" , async ( ) => {
1064
+ const { logger} = createMockOutputChannelWithLogger ( ) ;
1065
+
1066
+ // Mock makeCoderSdk to return a client that fails auth
1067
+ const { makeCoderSdk} = await import ( "./api" ) ;
1068
+ const mockSdkClient = {
1069
+ getAuthenticatedUser :vi
1070
+ . fn ( )
1071
+ . mockRejectedValue ( new Error ( "Authentication failed" ) ) ,
1072
+ } ;
1073
+ vi . mocked ( makeCoderSdk ) . mockResolvedValue ( mockSdkClient as never ) ;
1074
+
1075
+ // Mock needToken to return false so we go into the non-token auth path
1076
+ const { needToken} = await import ( "./api" ) ;
1077
+ vi . mocked ( needToken ) . mockReturnValue ( false ) ;
1078
+
1079
+ // Mock getErrorMessage from coder/site
1080
+ const { getErrorMessage} = await import ( "coder/site/src/api/errors" ) ;
1081
+ vi . mocked ( getErrorMessage ) . mockReturnValue ( "Authentication failed" ) ;
1082
+
1083
+ // Mock showErrorMessage for vscodeProposed
1084
+ const mockVscodeProposed = {
1085
+ window :{
1086
+ showErrorMessage :vi . fn ( ) ,
1087
+ } ,
1088
+ } as unknown as typeof vscode ;
1089
+
1090
+ const mockRestClient = {
1091
+ setHost :vi . fn ( ) ,
1092
+ setSessionToken :vi . fn ( ) ,
1093
+ } as unknown as Api ;
1094
+
1095
+ // Create mock Storage that uses Logger
1096
+ const mockStorage = {
1097
+ writeToCoderOutputChannel :vi . fn ( ( msg :string ) => {
1098
+ logger . info ( msg ) ;
1099
+ } ) ,
1100
+ setUrl :vi . fn ( ) ,
1101
+ setSessionToken :vi . fn ( ) ,
1102
+ configureCli :vi . fn ( ) ,
1103
+ } as unknown as Storage ;
1104
+
1105
+ const commands = new Commands (
1106
+ mockVscodeProposed ,
1107
+ mockRestClient ,
1108
+ mockStorage ,
1109
+ ) ;
1110
+
1111
+ // Mock toSafeHost
1112
+ const { toSafeHost} = await import ( "./util" ) ;
1113
+ vi . mocked ( toSafeHost ) . mockReturnValue ( "test.coder.com" ) ;
1114
+
1115
+ // Call login with isAutologin = true (as string in args)
1116
+ await commands . login ( "https://test.coder.com" , "test-token" , "" , "true" ) ;
1117
+
1118
+ // Verify error was logged for autologin
1119
+ expect ( mockStorage . writeToCoderOutputChannel ) . toHaveBeenCalledWith (
1120
+ "Failed to log in to Coder server: Authentication failed" ,
1121
+ ) ;
1122
+
1123
+ const logs = logger . getLogs ( ) ;
1124
+ expect ( logs . length ) . toBe ( 1 ) ;
1125
+ expect ( logs [ 0 ] . message ) . toBe (
1126
+ "Failed to log in to Coder server: Authentication failed" ,
1127
+ ) ;
1128
+ expect ( logs [ 0 ] . level ) . toBe ( "INFO" ) ;
1129
+
1130
+ // Verify showErrorMessage was NOT called (since it's autologin)
1131
+ expect ( mockVscodeProposed . window . showErrorMessage ) . not . toHaveBeenCalled ( ) ;
1132
+ } ) ;
1133
+
1134
+ it ( "should work with Storage instance that has Logger set" , async ( ) => {
1135
+ const { logger} = createMockOutputChannelWithLogger ( ) ;
1136
+
1137
+ // Mock makeCoderSdk to return a client that fails auth
1138
+ const { makeCoderSdk} = await import ( "./api" ) ;
1139
+ const mockSdkClient = {
1140
+ getAuthenticatedUser :vi
1141
+ . fn ( )
1142
+ . mockRejectedValue ( new Error ( "Network error" ) ) ,
1143
+ } ;
1144
+ vi . mocked ( makeCoderSdk ) . mockResolvedValue ( mockSdkClient as never ) ;
1145
+
1146
+ // Mock needToken to return false
1147
+ const { needToken} = await import ( "./api" ) ;
1148
+ vi . mocked ( needToken ) . mockReturnValue ( false ) ;
1149
+
1150
+ // Mock getErrorMessage from coder/site
1151
+ const { getErrorMessage} = await import ( "coder/site/src/api/errors" ) ;
1152
+ vi . mocked ( getErrorMessage ) . mockReturnValue ( "Network error" ) ;
1153
+
1154
+ const mockVscodeProposed = {
1155
+ window :{
1156
+ showErrorMessage :vi . fn ( ) ,
1157
+ } ,
1158
+ } as unknown as typeof vscode ;
1159
+
1160
+ const mockRestClient = {
1161
+ setHost :vi . fn ( ) ,
1162
+ setSessionToken :vi . fn ( ) ,
1163
+ } as unknown as Api ;
1164
+
1165
+ // Simulate Storage with Logger
1166
+ const mockStorage = {
1167
+ writeToCoderOutputChannel :vi . fn ( ( msg :string ) => {
1168
+ logger . error ( msg ) ;
1169
+ } ) ,
1170
+ setUrl :vi . fn ( ) ,
1171
+ setSessionToken :vi . fn ( ) ,
1172
+ configureCli :vi . fn ( ) ,
1173
+ } as unknown as Storage ;
1174
+
1175
+ const commands = new Commands (
1176
+ mockVscodeProposed ,
1177
+ mockRestClient ,
1178
+ mockStorage ,
1179
+ ) ;
1180
+
1181
+ // Mock toSafeHost
1182
+ const { toSafeHost} = await import ( "./util" ) ;
1183
+ vi . mocked ( toSafeHost ) . mockReturnValue ( "example.coder.com" ) ;
1184
+
1185
+ // Call login with isAutologin = true (as string in args)
1186
+ await commands . login (
1187
+ "https://example.coder.com" ,
1188
+ "bad-token" ,
1189
+ "" ,
1190
+ "true" ,
1191
+ ) ;
1192
+
1193
+ // Verify error was logged through Logger
1194
+ const logs = logger . getLogs ( ) ;
1195
+ expect ( logs . length ) . toBeGreaterThan ( 0 ) ;
1196
+ const hasExpectedLog = logs . some ( ( log ) =>
1197
+ log . message . includes ( "Failed to log in to Coder server: Network error" ) ,
1198
+ ) ;
1199
+ expect ( hasExpectedLog ) . toBe ( true ) ;
1200
+ } ) ;
1201
+
1202
+ it ( "should show error dialog when not autologin" , async ( ) => {
1203
+ const { logger} = createMockOutputChannelWithLogger ( ) ;
1204
+
1205
+ // Mock makeCoderSdk to return a client that fails auth
1206
+ const { makeCoderSdk} = await import ( "./api" ) ;
1207
+ const mockSdkClient = {
1208
+ getAuthenticatedUser :vi
1209
+ . fn ( )
1210
+ . mockRejectedValue ( new Error ( "Invalid token" ) ) ,
1211
+ } ;
1212
+ vi . mocked ( makeCoderSdk ) . mockResolvedValue ( mockSdkClient as never ) ;
1213
+
1214
+ // Mock needToken to return false
1215
+ const { needToken} = await import ( "./api" ) ;
1216
+ vi . mocked ( needToken ) . mockReturnValue ( false ) ;
1217
+
1218
+ // Mock getErrorMessage from coder/site
1219
+ const { getErrorMessage} = await import ( "coder/site/src/api/errors" ) ;
1220
+ vi . mocked ( getErrorMessage ) . mockReturnValue ( "Invalid token" ) ;
1221
+
1222
+ // Mock showErrorMessage for vscodeProposed
1223
+ const showErrorMessageMock = vi . fn ( ) ;
1224
+ const mockVscodeProposed = {
1225
+ window :{
1226
+ showErrorMessage :showErrorMessageMock ,
1227
+ } ,
1228
+ } as unknown as typeof vscode ;
1229
+
1230
+ const mockRestClient = {
1231
+ setHost :vi . fn ( ) ,
1232
+ setSessionToken :vi . fn ( ) ,
1233
+ } as unknown as Api ;
1234
+
1235
+ // Create mock Storage that uses Logger
1236
+ const mockStorage = {
1237
+ writeToCoderOutputChannel :vi . fn ( ( msg :string ) => {
1238
+ logger . info ( msg ) ;
1239
+ } ) ,
1240
+ setUrl :vi . fn ( ) ,
1241
+ setSessionToken :vi . fn ( ) ,
1242
+ configureCli :vi . fn ( ) ,
1243
+ } as unknown as Storage ;
1244
+
1245
+ const commands = new Commands (
1246
+ mockVscodeProposed ,
1247
+ mockRestClient ,
1248
+ mockStorage ,
1249
+ ) ;
1250
+
1251
+ // Mock toSafeHost
1252
+ const { toSafeHost} = await import ( "./util" ) ;
1253
+ vi . mocked ( toSafeHost ) . mockReturnValue ( "test.coder.com" ) ;
1254
+
1255
+ // Call login with isAutologin = false (default)
1256
+ await commands . login ( "https://test.coder.com" , "test-token" ) ;
1257
+
1258
+ // Verify error dialog was shown (not logged)
1259
+ expect ( showErrorMessageMock ) . toHaveBeenCalledWith (
1260
+ "Failed to log in to Coder server" ,
1261
+ {
1262
+ detail :"Invalid token" ,
1263
+ modal :true ,
1264
+ useCustom :true ,
1265
+ } ,
1266
+ ) ;
1267
+
1268
+ // Verify writeToCoderOutputChannel was NOT called
1269
+ expect ( mockStorage . writeToCoderOutputChannel ) . not . toHaveBeenCalled ( ) ;
1270
+
1271
+ // Verify no logs were written
1272
+ const logs = logger . getLogs ( ) ;
1273
+ expect ( logs . length ) . toBe ( 0 ) ;
1274
+ } ) ;
1275
+ } ) ;
1052
1276
} ) ;