@@ -2,6 +2,7 @@ package apps
22
33import (
44 "bytes"
5+ "encoding/json"
56 "errors"
67 "io"
78 "io/fs"
@@ -18,6 +19,7 @@ import (
1819 "github.com/spf13/cobra"
1920 "github.com/stretchr/testify/assert"
2021 "github.com/stretchr/testify/require"
22+ "go.yaml.in/yaml/v3"
2123)
2224
2325func TestIsTextFile (t * testing.T ) {
@@ -1148,3 +1150,231 @@ func TestRunCreate_NameDotAndOutputDirAreMutuallyExclusive(t *testing.T) {
11481150 require .Error (t , err )
11491151 assert .ErrorIs (t , err , prompt .ErrNameDotWithOutputDir )
11501152}
1153+
1154+ func TestIsPreRenderedTemplate (t * testing.T ) {
1155+ tests := []struct {
1156+ name string
1157+ setup func (dir string )
1158+ expected bool
1159+ }{
1160+ {
1161+ name : "normal template with project_name dir" ,
1162+ setup : func (dir string ) {
1163+ require .NoError (t , os .MkdirAll (filepath .Join (dir , "{{.project_name}}" ), 0o755 ))
1164+ require .NoError (t , os .WriteFile (filepath .Join (dir , bundleConfigFile ), []byte ("bundle:\n name: myapp\n " ), 0o644 ))
1165+ },
1166+ expected : false ,
1167+ },
1168+ {
1169+ name : "pre-rendered template" ,
1170+ setup : func (dir string ) {
1171+ require .NoError (t , os .WriteFile (filepath .Join (dir , bundleConfigFile ), []byte ("bundle:\n name: myapp\n " ), 0o644 ))
1172+ },
1173+ expected : true ,
1174+ },
1175+ {
1176+ name : "no databricks.yml" ,
1177+ setup : func (dir string ) {
1178+ require .NoError (t , os .WriteFile (filepath .Join (dir , "README.md" ), []byte ("hello" ), 0o644 ))
1179+ },
1180+ expected : false ,
1181+ },
1182+ {
1183+ name : "databricks.yml with Go template syntax" ,
1184+ setup : func (dir string ) {
1185+ require .NoError (t , os .WriteFile (filepath .Join (dir , bundleConfigFile ), []byte ("bundle:\n name: {{.project_name}}\n " ), 0o644 ))
1186+ },
1187+ expected : false ,
1188+ },
1189+ {
1190+ name : "unreadable directory" ,
1191+ setup : func (_ string ) {
1192+ // dir is replaced with a nonexistent path below.
1193+ },
1194+ expected : false ,
1195+ },
1196+ }
1197+
1198+ for _ , tt := range tests {
1199+ t .Run (tt .name , func (t * testing.T ) {
1200+ dir := t .TempDir ()
1201+ if tt .name == "unreadable directory" {
1202+ dir = filepath .Join (dir , "nonexistent" )
1203+ } else {
1204+ tt .setup (dir )
1205+ }
1206+ assert .Equal (t , tt .expected , isPreRenderedTemplate (dir ))
1207+ })
1208+ }
1209+ }
1210+
1211+ func TestReplaceProjectName (t * testing.T ) {
1212+ tests := []struct {
1213+ name string
1214+ yml string
1215+ pkgJSON string // empty means no package.json
1216+ newName string
1217+ wantBundleName string
1218+ wantAppName string
1219+ wantPkgName string
1220+ wantErr bool
1221+ }{
1222+ {
1223+ name : "bundle and app name replaced" ,
1224+ yml : "bundle:\n name: old-app\n resources:\n apps:\n old-app:\n name: \" old-app\" \n " ,
1225+ newName : "new-app" ,
1226+ wantBundleName : "new-app" ,
1227+ wantAppName : "new-app" ,
1228+ },
1229+ {
1230+ name : "bundle name only, no resources" ,
1231+ yml : "bundle:\n name: old-app\n " ,
1232+ newName : "new-app" ,
1233+ wantBundleName : "new-app" ,
1234+ },
1235+ {
1236+ name : "no bundle.name, only app name" ,
1237+ yml : "resources:\n apps:\n myapp:\n name: \" myapp\" \n " ,
1238+ newName : "new-app" ,
1239+ wantAppName : "new-app" ,
1240+ },
1241+ {
1242+ name : "package.json round-trip" ,
1243+ yml : "bundle:\n name: old-app\n " ,
1244+ pkgJSON : `{"name":"old-app","version":"1.0.0"}` ,
1245+ newName : "new-app" ,
1246+ wantBundleName : "new-app" ,
1247+ wantPkgName : "new-app" ,
1248+ },
1249+ {
1250+ name : "no package.json" ,
1251+ yml : "bundle:\n name: old-app\n " ,
1252+ newName : "new-app" ,
1253+ wantBundleName : "new-app" ,
1254+ },
1255+ }
1256+
1257+ for _ , tt := range tests {
1258+ t .Run (tt .name , func (t * testing.T ) {
1259+ dir := t .TempDir ()
1260+
1261+ require .NoError (t , os .WriteFile (filepath .Join (dir , bundleConfigFile ), []byte (tt .yml ), 0o644 ))
1262+
1263+ if tt .pkgJSON != "" {
1264+ require .NoError (t , os .WriteFile (filepath .Join (dir , "package.json" ), []byte (tt .pkgJSON ), 0o644 ))
1265+ }
1266+
1267+ err := replaceProjectName (dir , tt .newName )
1268+ if tt .wantErr {
1269+ require .Error (t , err )
1270+ return
1271+ }
1272+ require .NoError (t , err )
1273+
1274+ // Verify databricks.yml via yaml.Node to avoid format sensitivity.
1275+ ymlData , err := os .ReadFile (filepath .Join (dir , bundleConfigFile ))
1276+ require .NoError (t , err )
1277+
1278+ var doc yaml.Node
1279+ require .NoError (t , yaml .Unmarshal (ymlData , & doc ))
1280+
1281+ if tt .wantBundleName != "" {
1282+ bundleName := yamlMapLookup (yamlMapLookup (doc .Content [0 ], "bundle" ), "name" )
1283+ require .NotNil (t , bundleName , "bundle.name not found in output" )
1284+ assert .Equal (t , tt .wantBundleName , bundleName .Value )
1285+ }
1286+
1287+ if tt .wantAppName != "" {
1288+ appsNode := yamlMapLookup (yamlMapLookup (doc .Content [0 ], "resources" ), "apps" )
1289+ require .NotNil (t , appsNode )
1290+ require .True (t , appsNode .Kind == yaml .MappingNode && len (appsNode .Content ) >= 2 )
1291+ appName := yamlMapLookup (appsNode .Content [1 ], "name" )
1292+ require .NotNil (t , appName , "resources.apps.*.name not found in output" )
1293+ assert .Equal (t , tt .wantAppName , appName .Value )
1294+ }
1295+
1296+ if tt .wantPkgName != "" {
1297+ pkgData , err := os .ReadFile (filepath .Join (dir , "package.json" ))
1298+ require .NoError (t , err )
1299+ var pkg map [string ]any
1300+ require .NoError (t , json .Unmarshal (pkgData , & pkg ))
1301+ assert .Equal (t , tt .wantPkgName , pkg ["name" ])
1302+ }
1303+ })
1304+ }
1305+ }
1306+
1307+ func TestReplaceProjectNameNoDatabricksYml (t * testing.T ) {
1308+ dir := t .TempDir ()
1309+ err := replaceProjectName (dir , "new-app" )
1310+ require .Error (t , err )
1311+ }
1312+
1313+ func TestSetYAMLValue (t * testing.T ) {
1314+ input := "top:\n nested:\n leaf: old\n "
1315+ var doc yaml.Node
1316+ require .NoError (t , yaml .Unmarshal ([]byte (input ), & doc ))
1317+
1318+ setYAMLValue (& doc , []string {"top" , "nested" , "leaf" }, "new" )
1319+
1320+ val := yamlMapLookup (yamlMapLookup (yamlMapLookup (doc .Content [0 ], "top" ), "nested" ), "leaf" )
1321+ require .NotNil (t , val )
1322+ assert .Equal (t , "new" , val .Value )
1323+ }
1324+
1325+ func TestSetYAMLValueMissingKey (t * testing.T ) {
1326+ input := "top:\n nested:\n leaf: old\n "
1327+ var doc yaml.Node
1328+ require .NoError (t , yaml .Unmarshal ([]byte (input ), & doc ))
1329+
1330+ // Should not panic or modify the tree.
1331+ setYAMLValue (& doc , []string {"top" , "nonexistent" , "leaf" }, "new" )
1332+
1333+ val := yamlMapLookup (yamlMapLookup (yamlMapLookup (doc .Content [0 ], "top" ), "nested" ), "leaf" )
1334+ require .NotNil (t , val )
1335+ assert .Equal (t , "old" , val .Value )
1336+ }
1337+
1338+ func TestYamlMapLookup (t * testing.T ) {
1339+ input := "a: 1\n b: 2\n "
1340+ var doc yaml.Node
1341+ require .NoError (t , yaml .Unmarshal ([]byte (input ), & doc ))
1342+
1343+ root := doc .Content [0 ]
1344+ assert .NotNil (t , yamlMapLookup (root , "a" ))
1345+ assert .Equal (t , "1" , yamlMapLookup (root , "a" ).Value )
1346+ assert .Nil (t , yamlMapLookup (root , "missing" ))
1347+ assert .Nil (t , yamlMapLookup (nil , "a" ))
1348+ }
1349+
1350+ func TestSetFirstAppName (t * testing.T ) {
1351+ input := "resources:\n apps:\n myapp:\n name: \" old-name\" \n description: keep\n "
1352+ var doc yaml.Node
1353+ require .NoError (t , yaml .Unmarshal ([]byte (input ), & doc ))
1354+
1355+ setFirstAppName (& doc , "new-name" )
1356+
1357+ appsNode := yamlMapLookup (yamlMapLookup (doc .Content [0 ], "resources" ), "apps" )
1358+ require .NotNil (t , appsNode )
1359+ appName := yamlMapLookup (appsNode .Content [1 ], "name" )
1360+ require .NotNil (t , appName )
1361+ assert .Equal (t , "new-name" , appName .Value )
1362+
1363+ // Verify description is untouched.
1364+ desc := yamlMapLookup (appsNode .Content [1 ], "description" )
1365+ require .NotNil (t , desc )
1366+ assert .Equal (t , "keep" , desc .Value )
1367+ }
1368+
1369+ func TestSetFirstAppNameNoResources (t * testing.T ) {
1370+ input := "bundle:\n name: myapp\n "
1371+ var doc yaml.Node
1372+ require .NoError (t , yaml .Unmarshal ([]byte (input ), & doc ))
1373+
1374+ // Should not panic when resources.apps is absent.
1375+ setFirstAppName (& doc , "new-name" )
1376+
1377+ bundleName := yamlMapLookup (yamlMapLookup (doc .Content [0 ], "bundle" ), "name" )
1378+ require .NotNil (t , bundleName )
1379+ assert .Equal (t , "myapp" , bundleName .Value )
1380+ }
0 commit comments