44import { app , dialog , BrowserWindow } from 'electron' ;
55import fs from 'fs-extra' ;
66import { sendIpcEventToRenderer } from 'src/ipc-utils' ;
7+ import { validateBlueprintData } from 'src/lib/blueprint-features' ;
78import { handleAddSiteWithBlueprint } from 'src/lib/deeplink/handlers/add-site-blueprint-with-url' ;
89import { download } from 'src/lib/download' ;
910import { getMainWindow } from 'src/main-window' ;
@@ -13,6 +14,9 @@ jest.mock( 'fs-extra' );
1314jest . mock ( 'src/ipc-utils' ) ;
1415jest . mock ( 'src/lib/download' ) ;
1516jest . mock ( 'src/main-window' ) ;
17+ jest . mock ( 'src/lib/blueprint-features' , ( ) => ( {
18+ validateBlueprintData : jest . fn ( ) ,
19+ } ) ) ;
1620
1721// Silence console.error output
1822beforeAll ( ( ) => {
@@ -30,8 +34,14 @@ describe( 'handleAddSiteWithBlueprint', () => {
3034 focus : jest . fn ( ) ,
3135 } as unknown as BrowserWindow ;
3236
37+ const createBlueprintUrl = ( blueprintUrl : string ) => {
38+ const encodedUrl = encodeURIComponent ( blueprintUrl ) ;
39+ return new URL ( `wp-studio://add-site?blueprint_url=${ encodedUrl } ` ) ;
40+ } ;
41+
3342 beforeEach ( ( ) => {
3443 jest . clearAllMocks ( ) ;
44+ ( mockMainWindow . isMinimized as jest . Mock ) . mockReturnValue ( false ) ;
3545 jest . mocked ( app . getPath ) . mockReturnValue ( '/tmp' ) ;
3646 ( fs . mkdir as unknown as jest . Mock ) . mockResolvedValue ( undefined ) ;
3747 jest . mocked ( getMainWindow ) . mockResolvedValue ( mockMainWindow ) ;
@@ -43,10 +53,11 @@ describe( 'handleAddSiteWithBlueprint', () => {
4353
4454 it ( 'should handle add-site with valid blueprint_url' , async ( ) => {
4555 const blueprintUrl = 'https://example.com/blueprint.json' ;
46- const encodedUrl = encodeURIComponent ( blueprintUrl ) ;
47- const url = new URL ( `wpcom-local-dev://add-site?blueprint_url=${ encodedUrl } ` ) ;
56+ const url = createBlueprintUrl ( blueprintUrl ) ;
4857
4958 jest . mocked ( download ) . mockResolvedValue ( undefined ) ;
59+ jest . mocked ( fs . readJson ) . mockResolvedValue ( { steps : [ ] } ) ;
60+ jest . mocked ( validateBlueprintData ) . mockResolvedValue ( { valid : true } ) ;
5061
5162 await handleAddSiteWithBlueprint ( url ) ;
5263
@@ -63,7 +74,7 @@ describe( 'handleAddSiteWithBlueprint', () => {
6374 } ) ;
6475
6576 it ( 'should not send event if blueprint_url parameter is missing' , async ( ) => {
66- const url = new URL ( 'wpcom-local-dev ://add-site' ) ;
77+ const url = new URL ( 'wp-studio ://add-site' ) ;
6778
6879 await handleAddSiteWithBlueprint ( url ) ;
6980
@@ -74,7 +85,7 @@ describe( 'handleAddSiteWithBlueprint', () => {
7485 it ( 'should handle invalid blueprint_url gracefully' , async ( ) => {
7586 const invalidUrl = 'not-a-valid-url' ;
7687 const encodedUrl = encodeURIComponent ( invalidUrl ) ;
77- const url = new URL ( `wpcom-local-dev ://add-site?blueprint_url=${ encodedUrl } ` ) ;
88+ const url = new URL ( `wp-studio ://add-site?blueprint_url=${ encodedUrl } ` ) ;
7889
7990 await handleAddSiteWithBlueprint ( url ) ;
8091
@@ -84,9 +95,7 @@ describe( 'handleAddSiteWithBlueprint', () => {
8495 } ) ;
8596
8697 it ( 'should handle download failure gracefully' , async ( ) => {
87- const blueprintUrl = 'https://example.com/blueprint.json' ;
88- const encodedUrl = encodeURIComponent ( blueprintUrl ) ;
89- const url = new URL ( `wpcom-local-dev://add-site?blueprint_url=${ encodedUrl } ` ) ;
98+ const url = createBlueprintUrl ( 'https://example.com/blueprint.json' ) ;
9099
91100 const downloadError = new Error ( 'Download failed' ) ;
92101 jest . mocked ( download ) . mockRejectedValue ( downloadError ) ;
@@ -106,12 +115,12 @@ describe( 'handleAddSiteWithBlueprint', () => {
106115 } ) ;
107116
108117 it ( 'should restore and focus window when minimized' , async ( ) => {
109- const blueprintUrl = 'https://example.com/blueprint.json' ;
110- const encodedUrl = encodeURIComponent ( blueprintUrl ) ;
111- const url = new URL ( `wpcom-local-dev://add-site?blueprint_url=${ encodedUrl } ` ) ;
118+ const url = createBlueprintUrl ( 'https://example.com/blueprint.json' ) ;
112119
113120 ( mockMainWindow . isMinimized as jest . Mock ) . mockReturnValue ( true ) ;
114121 jest . mocked ( download ) . mockResolvedValue ( undefined ) ;
122+ ( fs . readJson as unknown as jest . Mock ) . mockResolvedValue ( { steps : [ ] } ) ;
123+ jest . mocked ( validateBlueprintData ) . mockResolvedValue ( { valid : true } ) ;
115124
116125 await handleAddSiteWithBlueprint ( url ) ;
117126
@@ -120,9 +129,7 @@ describe( 'handleAddSiteWithBlueprint', () => {
120129 } ) ;
121130
122131 it ( 'should handle cleanup errors gracefully on download failure' , async ( ) => {
123- const blueprintUrl = 'https://example.com/blueprint.json' ;
124- const encodedUrl = encodeURIComponent ( blueprintUrl ) ;
125- const url = new URL ( `wpcom-local-dev://add-site?blueprint_url=${ encodedUrl } ` ) ;
132+ const url = createBlueprintUrl ( 'https://example.com/blueprint.json' ) ;
126133
127134 const downloadError = new Error ( 'Download failed' ) ;
128135 jest . mocked ( download ) . mockRejectedValue ( downloadError ) ;
@@ -133,6 +140,30 @@ describe( 'handleAddSiteWithBlueprint', () => {
133140 expect ( dialog . showMessageBox ) . toHaveBeenCalled ( ) ;
134141 } ) ;
135142
143+ it ( 'should handle invalid blueprint and show error dialog' , async ( ) => {
144+ const url = createBlueprintUrl ( 'https://example.com/blueprint.json' ) ;
145+
146+ jest . mocked ( download ) . mockResolvedValue ( undefined ) ;
147+ ( fs . readJson as unknown as jest . Mock ) . mockResolvedValue ( { invalid : 'data' } ) ;
148+ jest . mocked ( validateBlueprintData ) . mockResolvedValue ( {
149+ valid : false ,
150+ error : 'Invalid blueprint format' ,
151+ } ) ;
152+ ( fs . remove as unknown as jest . Mock ) . mockResolvedValue ( undefined ) ;
153+
154+ await handleAddSiteWithBlueprint ( url ) ;
155+
156+ expect ( download ) . toHaveBeenCalled ( ) ;
157+ expect ( sendIpcEventToRenderer ) . not . toHaveBeenCalled ( ) ;
158+ expect ( fs . remove ) . toHaveBeenCalledWith ( expect . stringContaining ( 'blueprint-' ) ) ;
159+ expect ( dialog . showMessageBox ) . toHaveBeenCalledWith ( mockMainWindow , {
160+ type : 'error' ,
161+ message : expect . any ( String ) ,
162+ detail : 'Invalid blueprint format' ,
163+ buttons : expect . any ( Array ) ,
164+ } ) ;
165+ } ) ;
166+
136167 describe ( 'base64 blueprint handling' , ( ) => {
137168 it ( 'should handle add-site with valid base64-encoded blueprint' , async ( ) => {
138169 const blueprintData = {
@@ -141,7 +172,7 @@ describe( 'handleAddSiteWithBlueprint', () => {
141172 } ;
142173 const blueprintJson = JSON . stringify ( blueprintData ) ;
143174 const blueprintBase64 = Buffer . from ( blueprintJson ) . toString ( 'base64' ) ;
144- const url = new URL ( `wpcom-local-dev ://add-site?blueprint=${ blueprintBase64 } ` ) ;
175+ const url = new URL ( `wp-studio ://add-site?blueprint=${ blueprintBase64 } ` ) ;
145176
146177 await handleAddSiteWithBlueprint ( url ) ;
147178
@@ -152,7 +183,7 @@ describe( 'handleAddSiteWithBlueprint', () => {
152183 } ) ;
153184
154185 it ( 'should handle invalid base64-encoded blueprint and display error message' , async ( ) => {
155- const url = new URL ( 'wpcom-local-dev ://add-site?blueprint=invalid-base64!!!' ) ;
186+ const url = new URL ( 'wp-studio ://add-site?blueprint=invalid-base64!!!' ) ;
156187 await handleAddSiteWithBlueprint ( url ) ;
157188
158189 expect ( sendIpcEventToRenderer ) . not . toHaveBeenCalled ( ) ;
0 commit comments