github.com/iasthc/atlas/cmd/atlas@v0.0.0-20230523071841-73246df3f88d/internal/cmdapi/project_test.go (about)

     1  // Copyright 2021-present The Atlas Authors. All rights reserved.
     2  // This source code is licensed under the Apache 2.0 license found
     3  // in the LICENSE file in the root directory of this source tree.
     4  
     5  package cmdapi
     6  
     7  import (
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"testing"
    12  
    13  	"github.com/iasthc/atlas/cmd/atlas/internal/cmdext"
    14  	"github.com/iasthc/atlas/schemahcl"
    15  	"github.com/iasthc/atlas/sql/schema"
    16  
    17  	"github.com/stretchr/testify/require"
    18  	"github.com/zclconf/go-cty/cty"
    19  )
    20  
    21  func TestEnvByName(t *testing.T) {
    22  	d := t.TempDir()
    23  	h := `
    24  variable "name" {
    25  	type = string
    26  	default = "hello"
    27  }
    28  
    29  locals {
    30  	envName = atlas.env
    31  }
    32  
    33  lint {
    34  	destructive {
    35  		error = true
    36  	}
    37  	// Backwards compatibility with old attribute.
    38  	log = <<EOS
    39  {{- range $f := .Files }}
    40  	{{- $f.Name }}
    41  {{- end }}
    42  EOS
    43  }
    44  
    45  diff {
    46    skip {
    47      drop_schema = true
    48    }
    49  }
    50  
    51  env "local" {
    52  	url = "mysql://root:pass@localhost:3306/"
    53  	dev = "docker://mysql/8"
    54  	src = "${local.envName}/app.hcl"
    55  	schemas = ["hello", "world"]
    56  	migration {
    57  		dir = "file://migrations"
    58  		format = atlas
    59  		lock_timeout = "1s"
    60  		revisions_schema = "revisions"
    61  	}
    62  	lint {
    63  		latest = 1
    64  	}
    65  	diff {
    66  		skip {
    67  			drop_column = true
    68  		}
    69  	}
    70  	bool = true
    71  	integer = 42
    72  	str = var.name
    73  }
    74  
    75  env "multi" {
    76  	url = "mysql://root:pass@localhost:3306/"
    77  	src = [
    78  		"./a.hcl",
    79  		"./b.hcl",
    80  	]
    81  	lint {
    82  		git {
    83  			dir  = "./path"
    84  			base = "master"
    85  		}
    86  	}
    87  }
    88  `
    89  	path := filepath.Join(d, "atlas.hcl")
    90  	err := os.WriteFile(path, []byte(h), 0600)
    91  	require.NoError(t, err)
    92  	GlobalFlags.ConfigURL = "file://" + path
    93  	t.Run("ok", func(t *testing.T) {
    94  		_, envs, err := EnvByName("local")
    95  		require.NoError(t, err)
    96  		require.Len(t, envs, 1)
    97  		env := envs[0]
    98  		sort.Slice(env.Extra.Attrs, func(i, j int) bool {
    99  			return env.Extra.Attrs[i].K < env.Extra.Attrs[j].K
   100  		})
   101  		require.EqualValues(t, &Env{
   102  			Name:    "local",
   103  			URL:     "mysql://root:pass@localhost:3306/",
   104  			DevURL:  "docker://mysql/8",
   105  			Schemas: []string{"hello", "world"},
   106  			Migration: &Migration{
   107  				Dir:             "file://migrations",
   108  				Format:          formatAtlas,
   109  				LockTimeout:     "1s",
   110  				RevisionsSchema: "revisions",
   111  			},
   112  			Diff: &Diff{
   113  				SkipChanges: &SkipChanges{
   114  					DropColumn: true,
   115  				},
   116  			},
   117  			Lint: &Lint{
   118  				Latest: 1,
   119  				Format: "{{- range $f := .Files }}\n\t{{- $f.Name }}\n{{- end }}\n",
   120  				DefaultExtension: schemahcl.DefaultExtension{
   121  					Extra: schemahcl.Resource{
   122  						Children: []*schemahcl.Resource{
   123  							{
   124  								Type: "destructive",
   125  								Attrs: []*schemahcl.Attr{
   126  									schemahcl.BoolAttr("error", true),
   127  								},
   128  							},
   129  						},
   130  					},
   131  				},
   132  			},
   133  			DefaultExtension: schemahcl.DefaultExtension{
   134  				Extra: schemahcl.Resource{
   135  					Attrs: []*schemahcl.Attr{
   136  						schemahcl.BoolAttr("bool", true),
   137  						schemahcl.IntAttr("integer", 42),
   138  						schemahcl.StringAttr("src", "local/app.hcl"),
   139  						schemahcl.StringAttr("str", "hello"),
   140  					},
   141  				},
   142  			},
   143  			cfg: &cmdext.AtlasConfig{},
   144  		}, env)
   145  		sources, err := env.Sources()
   146  		require.NoError(t, err)
   147  		require.EqualValues(t, []string{"local/app.hcl"}, sources)
   148  	})
   149  	t.Run("multi", func(t *testing.T) {
   150  		_, envs, err := EnvByName("multi")
   151  		require.NoError(t, err)
   152  		require.Len(t, envs, 1)
   153  		srcs, err := envs[0].Sources()
   154  		require.NoError(t, err)
   155  		require.EqualValues(t, []string{"./a.hcl", "./b.hcl"}, srcs)
   156  	})
   157  	t.Run("with input", func(t *testing.T) {
   158  		_, envs, err := EnvByName("local", WithInput(map[string]cty.Value{
   159  			"name": cty.StringVal("goodbye"),
   160  		}))
   161  		require.NoError(t, err)
   162  		require.Len(t, envs, 1)
   163  		str, ok := envs[0].Attr("str")
   164  		require.True(t, ok)
   165  		val, err := str.String()
   166  		require.NoError(t, err)
   167  		require.EqualValues(t, "goodbye", val)
   168  	})
   169  	t.Run("wrong env", func(t *testing.T) {
   170  		_, _, err = EnvByName("home")
   171  		require.EqualError(t, err, `env "home" not defined in project file`)
   172  	})
   173  	t.Run("wrong dir", func(t *testing.T) {
   174  		GlobalFlags.ConfigURL = defaultConfigPath
   175  		_, _, err = EnvByName("home")
   176  		require.ErrorContains(t, err, `no such file or directory`)
   177  	})
   178  }
   179  
   180  func TestUnnamedEnv(t *testing.T) {
   181  	h := `
   182  env {
   183    name = atlas.env
   184    log {
   185      schema {
   186        apply = "env: ${atlas.env}"
   187      }
   188    }
   189  }`
   190  	path := filepath.Join(t.TempDir(), "atlas.hcl")
   191  	err := os.WriteFile(path, []byte(h), 0600)
   192  	require.NoError(t, err)
   193  	GlobalFlags.ConfigURL = "file://" + path
   194  	_, envs, err := EnvByName("local")
   195  	require.NoError(t, err)
   196  	require.Len(t, envs, 1)
   197  	require.Equal(t, "local", envs[0].Name)
   198  	require.Equal(t, "env: local", envs[0].Format.Schema.Apply)
   199  }
   200  
   201  func TestNoEnv(t *testing.T) {
   202  	h := `
   203  env "dev" {
   204    log {
   205      schema {
   206        apply = "env: ${atlas.env}"
   207      }
   208    }
   209  }
   210  
   211  env {
   212    name = atlas.env
   213    log {
   214      schema {
   215        apply = "env: ${atlas.env}"
   216      }
   217    }
   218  }
   219  
   220  lint {
   221    latest = 1
   222  }
   223  
   224  diff {
   225    skip {
   226      drop_column = true
   227    }
   228  }
   229  `
   230  	path := filepath.Join(t.TempDir(), "atlas.hcl")
   231  	err := os.WriteFile(path, []byte(h), 0600)
   232  	require.NoError(t, err)
   233  	GlobalFlags.ConfigURL = "file://" + path
   234  	project, envs, err := EnvByName("")
   235  	require.NoError(t, err)
   236  	require.Len(t, envs, 0)
   237  	require.Equal(t, 1, project.Lint.Latest)
   238  	require.NotNil(t, project.Diff.SkipChanges)
   239  	require.True(t, project.Diff.SkipChanges.DropColumn)
   240  }
   241  
   242  func TestPartialParse(t *testing.T) {
   243  	h := `
   244  data "remote_dir" "ignored" {
   245    name = "ignored"
   246  }
   247  
   248  locals {
   249    a = "b"
   250  }
   251  
   252  env {
   253    name = atlas.env
   254    log {
   255      schema {
   256        diff  = local.a
   257        apply = "env: ${atlas.env}"
   258      }
   259    }
   260    a = local.a
   261  }
   262  
   263  env "dev" {
   264    a = local.a
   265  }`
   266  	path := filepath.Join(t.TempDir(), "atlas.hcl")
   267  	err := os.WriteFile(path, []byte(h), 0600)
   268  	require.NoError(t, err)
   269  	GlobalFlags.ConfigURL = "file://" + path
   270  	_, envs, err := EnvByName("unnamed")
   271  	require.NoError(t, err)
   272  	require.Len(t, envs, 1)
   273  	require.Equal(t, "unnamed", envs[0].Name)
   274  	require.Equal(t, "b", envs[0].Format.Schema.Diff)
   275  	require.Equal(t, "env: unnamed", envs[0].Format.Schema.Apply)
   276  	_, envs, err = EnvByName("dev")
   277  	require.NoError(t, err)
   278  	require.Len(t, envs, 2)
   279  	for _, e := range envs {
   280  		attr, ok := e.Extra.Attr("a")
   281  		require.True(t, ok)
   282  		v, err := attr.String()
   283  		require.NoError(t, err)
   284  		require.Equal(t, "b", v)
   285  	}
   286  }
   287  
   288  func TestDiff_Options(t *testing.T) {
   289  	d := &Diff{}
   290  	require.Len(t, d.Options(), 1)
   291  	d.SkipChanges = &SkipChanges{}
   292  	require.Len(t, d.Options(), 1)
   293  
   294  	d.SkipChanges = &SkipChanges{DropSchema: true}
   295  	require.Len(t, d.Options(), 2)
   296  	opts := schema.NewDiffOptions(d.Options()...)
   297  	require.True(t, opts.Skipped(&schema.DropSchema{}))
   298  	require.False(t, opts.Skipped(&schema.DropTable{}))
   299  
   300  	d.SkipChanges = &SkipChanges{DropSchema: true, DropTable: true}
   301  	require.Len(t, d.Options(), 2)
   302  	opts = schema.NewDiffOptions(d.Options()...)
   303  	require.True(t, opts.Skipped(&schema.DropSchema{}))
   304  	require.True(t, opts.Skipped(&schema.DropTable{}))
   305  }