@@ -5,7 +5,13 @@ import { CancellationTokenSource, EventEmitter, NotebookCell, NotebookCellKind,
55import { IDisposableRegistry } from '../../common/types' ;
66import { IntegrationStorage } from './integrationStorage' ;
77import { SqlIntegrationEnvironmentVariablesProvider } from './sqlIntegrationEnvironmentVariablesProvider' ;
8- import { IntegrationType , PostgresIntegrationConfig , BigQueryIntegrationConfig } from './integrationTypes' ;
8+ import {
9+ IntegrationType ,
10+ PostgresIntegrationConfig ,
11+ BigQueryIntegrationConfig ,
12+ SnowflakeIntegrationConfig ,
13+ SnowflakeAuthMethods
14+ } from './integrationTypes' ;
915import { mockedVSCodeNamespaces , resetVSCodeMocks } from '../../../test/vscode-mock' ;
1016
1117suite ( 'SqlIntegrationEnvironmentVariablesProvider' , ( ) => {
@@ -366,6 +372,299 @@ suite('SqlIntegrationEnvironmentVariablesProvider', () => {
366372 const envVars = await provider . getEnvironmentVariables ( uri , cts . token ) ;
367373 assert . deepStrictEqual ( envVars , { } ) ;
368374 } ) ;
375+
376+ suite ( 'Snowflake Integration' , ( ) => {
377+ test ( 'Returns environment variable for Snowflake with PASSWORD auth' , async ( ) => {
378+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
379+ const integrationId = 'my-snowflake' ;
380+ const config : SnowflakeIntegrationConfig = {
381+ id : integrationId ,
382+ name : 'My Snowflake' ,
383+ type : IntegrationType . Snowflake ,
384+ account : 'myorg-myaccount' ,
385+ warehouse : 'COMPUTE_WH' ,
386+ database : 'MYDB' ,
387+ role : 'ANALYST' ,
388+ authMethod : SnowflakeAuthMethods . PASSWORD ,
389+ username : 'john.doe' ,
390+ password : 'secret123'
391+ } ;
392+
393+ const notebook = createMockNotebook ( uri , [
394+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT * FROM customers' , {
395+ sql_integration_id : integrationId
396+ } )
397+ ] ) ;
398+
399+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
400+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
401+
402+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
403+
404+ assert . property ( envVars , 'SQL_MY_SNOWFLAKE' ) ;
405+ const credentialsJson = JSON . parse ( envVars [ 'SQL_MY_SNOWFLAKE' ] ! ) ;
406+ assert . strictEqual (
407+ credentialsJson . url ,
408+ 'snowflake://john.doe:secret123@myorg-myaccount/MYDB?warehouse=COMPUTE_WH&role=ANALYST&application=Deepnote'
409+ ) ;
410+ assert . deepStrictEqual ( credentialsJson . params , { } ) ;
411+ assert . strictEqual ( credentialsJson . param_style , 'format' ) ;
412+ } ) ;
413+
414+ test ( 'Returns environment variable for Snowflake with legacy null auth (username+password)' , async ( ) => {
415+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
416+ const integrationId = 'legacy-snowflake' ;
417+ const config : SnowflakeIntegrationConfig = {
418+ id : integrationId ,
419+ name : 'Legacy Snowflake' ,
420+ type : IntegrationType . Snowflake ,
421+ account : 'legacy-account' ,
422+ warehouse : 'WH' ,
423+ database : 'DB' ,
424+ authMethod : null ,
425+ username : 'user' ,
426+ password : 'pass'
427+ } ;
428+
429+ const notebook = createMockNotebook ( uri , [
430+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT 1' , {
431+ sql_integration_id : integrationId
432+ } )
433+ ] ) ;
434+
435+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
436+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
437+
438+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
439+
440+ assert . property ( envVars , 'SQL_LEGACY_SNOWFLAKE' ) ;
441+ const credentialsJson = JSON . parse ( envVars [ 'SQL_LEGACY_SNOWFLAKE' ] ! ) ;
442+ assert . strictEqual (
443+ credentialsJson . url ,
444+ 'snowflake://user:pass@legacy-account/DB?warehouse=WH&application=Deepnote'
445+ ) ;
446+ assert . deepStrictEqual ( credentialsJson . params , { } ) ;
447+ } ) ;
448+
449+ test ( 'Returns environment variable for Snowflake with SERVICE_ACCOUNT_KEY_PAIR auth' , async ( ) => {
450+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
451+ const integrationId = 'snowflake-keypair' ;
452+ const privateKey = '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----' ;
453+ const config : SnowflakeIntegrationConfig = {
454+ id : integrationId ,
455+ name : 'Snowflake KeyPair' ,
456+ type : IntegrationType . Snowflake ,
457+ account : 'keypair-account' ,
458+ warehouse : 'ETL_WH' ,
459+ database : 'PROD_DB' ,
460+ role : 'ETL_ROLE' ,
461+ authMethod : SnowflakeAuthMethods . SERVICE_ACCOUNT_KEY_PAIR ,
462+ username : 'service_account' ,
463+ privateKey : privateKey ,
464+ privateKeyPassphrase : 'passphrase123'
465+ } ;
466+
467+ const notebook = createMockNotebook ( uri , [
468+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT * FROM events' , {
469+ sql_integration_id : integrationId
470+ } )
471+ ] ) ;
472+
473+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
474+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
475+
476+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
477+
478+ assert . property ( envVars , 'SQL_SNOWFLAKE_KEYPAIR' ) ;
479+ const credentialsJson = JSON . parse ( envVars [ 'SQL_SNOWFLAKE_KEYPAIR' ] ! ) ;
480+ assert . strictEqual (
481+ credentialsJson . url ,
482+ 'snowflake://service_account@keypair-account/PROD_DB?warehouse=ETL_WH&role=ETL_ROLE&authenticator=snowflake_jwt&application=Deepnote'
483+ ) ;
484+ assert . deepStrictEqual ( credentialsJson . params , {
485+ private_key : privateKey ,
486+ private_key_passphrase : 'passphrase123'
487+ } ) ;
488+ assert . strictEqual ( credentialsJson . param_style , 'format' ) ;
489+ } ) ;
490+
491+ test ( 'Returns environment variable for Snowflake with SERVICE_ACCOUNT_KEY_PAIR auth without passphrase' , async ( ) => {
492+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
493+ const integrationId = 'snowflake-keypair-no-pass' ;
494+ const privateKey = '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----' ;
495+ const config : SnowflakeIntegrationConfig = {
496+ id : integrationId ,
497+ name : 'Snowflake KeyPair No Pass' ,
498+ type : IntegrationType . Snowflake ,
499+ account : 'account123' ,
500+ warehouse : 'WH' ,
501+ database : 'DB' ,
502+ authMethod : SnowflakeAuthMethods . SERVICE_ACCOUNT_KEY_PAIR ,
503+ username : 'svc_user' ,
504+ privateKey : privateKey
505+ } ;
506+
507+ const notebook = createMockNotebook ( uri , [
508+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT 1' , {
509+ sql_integration_id : integrationId
510+ } )
511+ ] ) ;
512+
513+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
514+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
515+
516+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
517+
518+ assert . property ( envVars , 'SQL_SNOWFLAKE_KEYPAIR_NO_PASS' ) ;
519+ const credentialsJson = JSON . parse ( envVars [ 'SQL_SNOWFLAKE_KEYPAIR_NO_PASS' ] ! ) ;
520+ assert . strictEqual (
521+ credentialsJson . url ,
522+ 'snowflake://svc_user@account123/DB?warehouse=WH&authenticator=snowflake_jwt&application=Deepnote'
523+ ) ;
524+ assert . deepStrictEqual ( credentialsJson . params , {
525+ private_key : privateKey
526+ } ) ;
527+ } ) ;
528+
529+ test ( 'Properly encodes special characters in Snowflake credentials' , async ( ) => {
530+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
531+ const integrationId = 'snowflake-special' ;
532+ const config : SnowflakeIntegrationConfig = {
533+ id : integrationId ,
534+ name : 'Snowflake Special' ,
535+ type : IntegrationType . Snowflake ,
536+ account : 'my-org.account' ,
537+ warehouse : 'WH@2024' ,
538+ database : 'DB:TEST' ,
539+ role : 'ROLE#1' ,
540+ authMethod : SnowflakeAuthMethods . PASSWORD ,
541+ 542+ password : 'p@ss:word!#$%'
543+ } ;
544+
545+ const notebook = createMockNotebook ( uri , [
546+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT 1' , {
547+ sql_integration_id : integrationId
548+ } )
549+ ] ) ;
550+
551+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
552+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
553+
554+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
555+
556+ assert . property ( envVars , 'SQL_SNOWFLAKE_SPECIAL' ) ;
557+ const credentialsJson = JSON . parse ( envVars [ 'SQL_SNOWFLAKE_SPECIAL' ] ! ) ;
558+ // Verify URL encoding of special characters
559+ assert . strictEqual (
560+ credentialsJson . url ,
561+ 'snowflake://user%40domain.com:p%40ss%3Aword!%23%24%[email protected] /DB%3ATEST?warehouse=WH%402024&role=ROLE%231&application=Deepnote' 562+ ) ;
563+ } ) ;
564+
565+ test ( 'Handles Snowflake with minimal optional fields' , async ( ) => {
566+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
567+ const integrationId = 'snowflake-minimal' ;
568+ const config : SnowflakeIntegrationConfig = {
569+ id : integrationId ,
570+ name : 'Snowflake Minimal' ,
571+ type : IntegrationType . Snowflake ,
572+ account : 'minimal-account' ,
573+ authMethod : SnowflakeAuthMethods . PASSWORD ,
574+ username : 'user' ,
575+ password : 'pass'
576+ } ;
577+
578+ const notebook = createMockNotebook ( uri , [
579+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT 1' , {
580+ sql_integration_id : integrationId
581+ } )
582+ ] ) ;
583+
584+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
585+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
586+
587+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
588+
589+ assert . property ( envVars , 'SQL_SNOWFLAKE_MINIMAL' ) ;
590+ const credentialsJson = JSON . parse ( envVars [ 'SQL_SNOWFLAKE_MINIMAL' ] ! ) ;
591+ // Should not include warehouse, database, or role in URL when not provided
592+ assert . strictEqual ( credentialsJson . url , 'snowflake://user:pass@minimal-account?application=Deepnote' ) ;
593+ } ) ;
594+
595+ test ( 'Throws error for unsupported Snowflake auth method (OKTA)' , async ( ) => {
596+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
597+ const integrationId = 'snowflake-okta' ;
598+ const config : SnowflakeIntegrationConfig = {
599+ id : integrationId ,
600+ name : 'Snowflake OKTA' ,
601+ type : IntegrationType . Snowflake ,
602+ account : 'okta-account' ,
603+ authMethod : SnowflakeAuthMethods . OKTA
604+ } ;
605+
606+ const notebook = createMockNotebook ( uri , [
607+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT 1' , {
608+ sql_integration_id : integrationId
609+ } )
610+ ] ) ;
611+
612+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
613+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
614+
615+ // Should return empty object when unsupported auth method is encountered
616+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
617+ assert . deepStrictEqual ( envVars , { } ) ;
618+ } ) ;
619+
620+ test ( 'Throws error for unsupported Snowflake auth method (AZURE_AD)' , async ( ) => {
621+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
622+ const integrationId = 'snowflake-azure' ;
623+ const config : SnowflakeIntegrationConfig = {
624+ id : integrationId ,
625+ name : 'Snowflake Azure' ,
626+ type : IntegrationType . Snowflake ,
627+ account : 'azure-account' ,
628+ authMethod : SnowflakeAuthMethods . AZURE_AD
629+ } ;
630+
631+ const notebook = createMockNotebook ( uri , [
632+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT 1' , {
633+ sql_integration_id : integrationId
634+ } )
635+ ] ) ;
636+
637+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
638+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
639+
640+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
641+ assert . deepStrictEqual ( envVars , { } ) ;
642+ } ) ;
643+
644+ test ( 'Throws error for unsupported Snowflake auth method (KEY_PAIR)' , async ( ) => {
645+ const uri = Uri . file ( '/test/notebook.deepnote' ) ;
646+ const integrationId = 'snowflake-keypair-user' ;
647+ const config : SnowflakeIntegrationConfig = {
648+ id : integrationId ,
649+ name : 'Snowflake KeyPair User' ,
650+ type : IntegrationType . Snowflake ,
651+ account : 'keypair-user-account' ,
652+ authMethod : SnowflakeAuthMethods . KEY_PAIR
653+ } ;
654+
655+ const notebook = createMockNotebook ( uri , [
656+ createMockCell ( 0 , NotebookCellKind . Code , 'sql' , 'SELECT 1' , {
657+ sql_integration_id : integrationId
658+ } )
659+ ] ) ;
660+
661+ when ( mockedVSCodeNamespaces . workspace . notebookDocuments ) . thenReturn ( [ notebook ] ) ;
662+ when ( integrationStorage . getIntegrationConfig ( integrationId ) ) . thenResolve ( config ) ;
663+
664+ const envVars = await provider . getEnvironmentVariables ( uri ) ;
665+ assert . deepStrictEqual ( envVars , { } ) ;
666+ } ) ;
667+ } ) ;
369668} ) ;
370669
371670function createMockNotebook ( uri : Uri , cells : NotebookCell [ ] ) : NotebookDocument {
0 commit comments