11package apps
22
33import (
4+ "context"
45 "errors"
56 "testing"
67
8+ "github.com/databricks/cli/libs/apps/manifest"
79 "github.com/databricks/cli/libs/apps/prompt"
810 "github.com/spf13/cobra"
911 "github.com/stretchr/testify/assert"
@@ -64,15 +66,15 @@ func TestIsTextFile(t *testing.T) {
6466 }
6567}
6668
67- func TestSubstituteVars (t * testing.T ) {
69+ func TestExecuteTemplate (t * testing.T ) {
70+ ctx := context .Background ()
6871 vars := templateVars {
6972 ProjectName : "my-app" ,
70- SQLWarehouseID : "warehouse123" ,
7173 AppDescription : "My awesome app" ,
7274 Profile : "default" ,
7375 WorkspaceHost : "https://dbc-123.cloud.databricks.com" ,
74- PluginImport : "analytics" ,
75- PluginUsage : "analytics()" ,
76+ PluginImports : "analytics" ,
77+ PluginUsages : "analytics()" ,
7678 }
7779
7880 tests := []struct {
@@ -85,11 +87,6 @@ func TestSubstituteVars(t *testing.T) {
8587 input : "name: {{.project_name}}" ,
8688 expected : "name: my-app" ,
8789 },
88- {
89- name : "warehouse id substitution" ,
90- input : "warehouse: {{.sql_warehouse_id}}" ,
91- expected : "warehouse: warehouse123" ,
92- },
9390 {
9491 name : "description substitution" ,
9592 input : "description: {{.app_description}}" ,
@@ -102,17 +99,17 @@ func TestSubstituteVars(t *testing.T) {
10299 },
103100 {
104101 name : "workspace host substitution" ,
105- input : "host: {{workspace_host}}" ,
102+ input : "host: {{. workspace_host}}" ,
106103 expected : "host: https://dbc-123.cloud.databricks.com" ,
107104 },
108105 {
109106 name : "plugin import substitution" ,
110- input : "import { {{.plugin_import }} } from 'appkit'" ,
107+ input : "import { {{.plugin_imports }} } from 'appkit'" ,
111108 expected : "import { analytics } from 'appkit'" ,
112109 },
113110 {
114111 name : "plugin usage substitution" ,
115- input : "plugins: [{{.plugin_usage }}]" ,
112+ input : "plugins: [{{.plugin_usages }}]" ,
116113 expected : "plugins: [analytics()]" ,
117114 },
118115 {
@@ -129,22 +126,20 @@ func TestSubstituteVars(t *testing.T) {
129126
130127 for _ , tt := range tests {
131128 t .Run (tt .name , func (t * testing.T ) {
132- result := substituteVars (tt .input , vars )
133- assert .Equal (t , tt .expected , result )
129+ result , err := executeTemplate (ctx , "test.txt" , []byte (tt .input ), vars )
130+ require .NoError (t , err )
131+ assert .Equal (t , tt .expected , string (result ))
134132 })
135133 }
136134}
137135
138- func TestSubstituteVarsNoPlugins (t * testing.T ) {
139- // Test plugin cleanup when no plugins are selected
136+ func TestExecuteTemplateEmptyPlugins (t * testing.T ) {
137+ ctx := context . Background ()
140138 vars := templateVars {
141139 ProjectName : "my-app" ,
142- SQLWarehouseID : "" ,
143140 AppDescription : "My app" ,
144- Profile : "" ,
145- WorkspaceHost : "" ,
146- PluginImport : "" , // No plugins
147- PluginUsage : "" ,
141+ PluginImports : "" ,
142+ PluginUsages : "" ,
148143 }
149144
150145 tests := []struct {
@@ -153,25 +148,35 @@ func TestSubstituteVarsNoPlugins(t *testing.T) {
153148 expected string
154149 }{
155150 {
156- name : "removes plugin import with comma " ,
157- input : "import { core, {{.plugin_import }} } from 'appkit'" ,
151+ name : "empty plugin imports render as empty " ,
152+ input : "import { core{{if .plugin_imports}} , {{.plugin_imports}}{{end }} } from 'appkit'" ,
158153 expected : "import { core } from 'appkit'" ,
159154 },
160155 {
161- name : "removes plugin usage line " ,
162- input : "plugins: [\n {{.plugin_usage }},\n ]" ,
163- expected : "plugins: [\n ]" ,
156+ name : "empty plugin usages render as empty " ,
157+ input : "plugins: [{{if .plugin_usages}} \n {{.plugin_usages }},\n {{end}} ]" ,
158+ expected : "plugins: []" ,
164159 },
165160 }
166161
167162 for _ , tt := range tests {
168163 t .Run (tt .name , func (t * testing.T ) {
169- result := substituteVars (tt .input , vars )
170- assert .Equal (t , tt .expected , result )
164+ result , err := executeTemplate (ctx , "test.txt" , []byte (tt .input ), vars )
165+ require .NoError (t , err )
166+ assert .Equal (t , tt .expected , string (result ))
171167 })
172168 }
173169}
174170
171+ func TestExecuteTemplateInvalidSyntaxReturnsOriginal (t * testing.T ) {
172+ ctx := context .Background ()
173+ vars := templateVars {ProjectName : "my-app" }
174+ input := "some content with bad {{ syntax"
175+ result , err := executeTemplate (ctx , "test.js" , []byte (input ), vars )
176+ require .NoError (t , err )
177+ assert .Equal (t , input , string (result ))
178+ }
179+
175180func TestInitCmdBranchAndVersionMutuallyExclusive (t * testing.T ) {
176181 cmd := newInitCmd ()
177182 cmd .PreRunE = nil // skip workspace client setup for flag validation test
@@ -283,3 +288,168 @@ func TestParseDeployAndRunFlags(t *testing.T) {
283288 })
284289 }
285290}
291+
292+ // testManifest returns a manifest with an "analytics" plugin for testing parseSetValues.
293+ func testManifest () * manifest.Manifest {
294+ return & manifest.Manifest {
295+ Plugins : map [string ]manifest.Plugin {
296+ "analytics" : {
297+ Name : "analytics" ,
298+ Resources : manifest.Resources {
299+ Required : []manifest.Resource {
300+ {
301+ Type : "sql_warehouse" ,
302+ Alias : "SQL Warehouse" ,
303+ ResourceKey : "sql-warehouse" ,
304+ Fields : map [string ]manifest.ResourceField {"id" : {Env : "WH_ID" }},
305+ },
306+ },
307+ Optional : []manifest.Resource {
308+ {
309+ Type : "database" ,
310+ Alias : "Database" ,
311+ ResourceKey : "database" ,
312+ Fields : map [string ]manifest.ResourceField {
313+ "instance_name" : {Env : "DB_INST" },
314+ "database_name" : {Env : "DB_NAME" },
315+ },
316+ },
317+ {
318+ Type : "secret" ,
319+ Alias : "Secret" ,
320+ ResourceKey : "secret" ,
321+ Fields : map [string ]manifest.ResourceField {
322+ "scope" : {Env : "SECRET_SCOPE" },
323+ "key" : {Env : "SECRET_KEY" },
324+ },
325+ },
326+ },
327+ },
328+ },
329+ },
330+ }
331+ }
332+
333+ func TestParseSetValues (t * testing.T ) {
334+ m := testManifest ()
335+
336+ tests := []struct {
337+ name string
338+ setValues []string
339+ wantRV map [string ]string
340+ wantErr string
341+ }{
342+ {
343+ name : "single field" ,
344+ setValues : []string {"analytics.sql-warehouse.id=abc123" },
345+ wantRV : map [string ]string {"sql-warehouse.id" : "abc123" },
346+ },
347+ {
348+ name : "multi-field complete" ,
349+ setValues : []string {"analytics.database.instance_name=inst" , "analytics.database.database_name=mydb" },
350+ wantRV : map [string ]string {"database.instance_name" : "inst" , "database.database_name" : "mydb" },
351+ },
352+ {
353+ name : "later set overrides earlier" ,
354+ setValues : []string {"analytics.sql-warehouse.id=first" , "analytics.sql-warehouse.id=second" },
355+ wantRV : map [string ]string {"sql-warehouse.id" : "second" },
356+ },
357+ {
358+ name : "empty set values" ,
359+ setValues : nil ,
360+ wantRV : map [string ]string {},
361+ },
362+ {
363+ name : "missing equals sign" ,
364+ setValues : []string {"analytics.sql-warehouse.id" },
365+ wantErr : "invalid --set format" ,
366+ },
367+ {
368+ name : "too few key parts" ,
369+ setValues : []string {"sql-warehouse.id=abc" },
370+ wantErr : "invalid --set key" ,
371+ },
372+ {
373+ name : "unknown plugin" ,
374+ setValues : []string {"nosuch.sql-warehouse.id=abc" },
375+ wantErr : `unknown plugin "nosuch"` ,
376+ },
377+ {
378+ name : "unknown resource key" ,
379+ setValues : []string {"analytics.nosuch.id=abc" },
380+ wantErr : `has no resource with key "nosuch"` ,
381+ },
382+ {
383+ name : "unknown field" ,
384+ setValues : []string {"analytics.sql-warehouse.nosuch=abc" },
385+ wantErr : `field "nosuch"` ,
386+ },
387+ {
388+ name : "multi-field incomplete database" ,
389+ setValues : []string {"analytics.database.instance_name=inst" },
390+ wantErr : `incomplete resource "database"` ,
391+ },
392+ {
393+ name : "multi-field incomplete secret" ,
394+ setValues : []string {"analytics.secret.scope=myscope" },
395+ wantErr : `incomplete resource "secret"` ,
396+ },
397+ {
398+ name : "all fields together" ,
399+ setValues : []string {
400+ "analytics.sql-warehouse.id=wh1" ,
401+ "analytics.database.instance_name=inst" ,
402+ "analytics.database.database_name=mydb" ,
403+ "analytics.secret.scope=s" ,
404+ "analytics.secret.key=k" ,
405+ },
406+ wantRV : map [string ]string {
407+ "sql-warehouse.id" : "wh1" ,
408+ "database.instance_name" : "inst" ,
409+ "database.database_name" : "mydb" ,
410+ "secret.scope" : "s" ,
411+ "secret.key" : "k" ,
412+ },
413+ },
414+ }
415+
416+ for _ , tt := range tests {
417+ t .Run (tt .name , func (t * testing.T ) {
418+ rv , err := parseSetValues (tt .setValues , m )
419+ if tt .wantErr != "" {
420+ require .Error (t , err )
421+ assert .Contains (t , err .Error (), tt .wantErr )
422+ } else {
423+ require .NoError (t , err )
424+ assert .Equal (t , tt .wantRV , rv )
425+ }
426+ })
427+ }
428+ }
429+
430+ func TestPluginHasResourceField (t * testing.T ) {
431+ m := testManifest ()
432+ p := m .GetPluginByName ("analytics" )
433+ require .NotNil (t , p )
434+
435+ assert .True (t , pluginHasResourceField (p , "sql-warehouse" , "id" ))
436+ assert .True (t , pluginHasResourceField (p , "database" , "instance_name" ))
437+ assert .True (t , pluginHasResourceField (p , "secret" , "scope" ))
438+ assert .False (t , pluginHasResourceField (p , "sql-warehouse" , "nosuch" ))
439+ assert .False (t , pluginHasResourceField (p , "nosuch" , "id" ))
440+ }
441+
442+ func TestAppendUnique (t * testing.T ) {
443+ result := appendUnique ([]string {"a" , "b" }, "b" , "c" , "a" , "d" )
444+ assert .Equal (t , []string {"a" , "b" , "c" , "d" }, result )
445+ }
446+
447+ func TestAppendUniqueEmptyBase (t * testing.T ) {
448+ result := appendUnique (nil , "x" , "y" , "x" )
449+ assert .Equal (t , []string {"x" , "y" }, result )
450+ }
451+
452+ func TestAppendUniqueNoValues (t * testing.T ) {
453+ result := appendUnique ([]string {"a" , "b" })
454+ assert .Equal (t , []string {"a" , "b" }, result )
455+ }
0 commit comments