@@ -4,6 +4,7 @@ import { describe, it, expect, vi, beforeAll } from "vitest";
44import * as vscode from "vscode" ;
55import { Commands } from "./commands" ;
66import { Storage } from "./storage" ;
7+ import { createMockOutputChannelWithLogger } from "./test-helpers" ;
78import { OpenableTreeItem } from "./workspacesProvider" ;
89
910// Mock dependencies
@@ -13,6 +14,14 @@ vi.mock("./error");
1314vi . mock ( "./storage" ) ;
1415vi . mock ( "./util" ) ;
1516vi . 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+ } ) ) ;
1625
1726beforeAll ( ( ) => {
1827vi . mock ( "vscode" , ( ) => {
@@ -1049,4 +1058,219 @@ describe("commands", () => {
10491058) ;
10501059} ) ;
10511060} ) ;
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+ } ) ;
10521276} ) ;