sq/cli/cmd_add_test.go
Neil O'Toole 99454852f0
db tools preliminary work; --src.schema changes (#392)
- Preliminary work on the (currently hidden) `db` cmds.
- Improvements to `--src.schema`
2024-02-09 09:08:39 -07:00

313 lines
7.5 KiB
Go

package cli_test
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/neilotoole/sq/cli/testrun"
"github.com/neilotoole/sq/libsq/core/options"
"github.com/neilotoole/sq/libsq/source"
"github.com/neilotoole/sq/libsq/source/drivertype"
"github.com/neilotoole/sq/testh"
"github.com/neilotoole/sq/testh/proj"
"github.com/neilotoole/sq/testh/sakila"
"github.com/neilotoole/sq/testh/tu"
)
func TestCmdAdd(t *testing.T) {
type query struct {
// q is the SLQ query to execute
q string
wantRows int
wantCols int
}
actorDataQuery := &query{
q: ".data",
wantRows: sakila.TblActorCount,
wantCols: len(sakila.TblActorCols()),
}
_ = actorDataQuery
testCases := []struct {
// Set only one of loc, or locFromHandle, to create
// the first arg to "add" cmd.
//
// loc, when set, will be used directly.
loc string
// locFromHandle, when set, gets the location from the
// config source with the given handle.
locFromHandle string
driver string // --driver flag
handle string // --handle flag
wantHandle string
wantType drivertype.Type
wantOptions options.Options
wantAddErr bool
wantQueryErr bool
query *query
}{
{
loc: "",
wantAddErr: true,
},
{
loc: " ",
wantAddErr: true,
},
{
loc: "/",
wantAddErr: true,
},
{
loc: "../../",
wantAddErr: true,
},
{
loc: "does/not/exist",
wantAddErr: true,
},
{
loc: "_",
wantAddErr: true,
},
{
loc: ".",
wantAddErr: true,
},
{
loc: "/",
wantAddErr: true,
},
{
loc: "../does/not/exist.csv",
wantAddErr: true,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: "@h1",
wantHandle: "@h1",
wantType: drivertype.CSV,
query: actorDataQuery,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: "@h1",
wantHandle: "@h1",
wantType: drivertype.CSV,
},
{
loc: proj.Abs(sakila.PathCSVActor),
wantHandle: "@actor",
wantType: drivertype.CSV,
},
{
loc: proj.Abs(sakila.PathCSVActor),
driver: "csv",
wantHandle: "@actor",
wantType: drivertype.CSV,
},
{
loc: proj.Abs(sakila.PathCSVActor),
driver: "xlsx",
wantHandle: "@actor",
wantType: drivertype.XLSX,
// It's legal to add a CSV file with the xlsx driver.
wantAddErr: false,
// But it should fail when we try to query it.
wantQueryErr: true,
},
{
loc: proj.Abs(sakila.PathTSVActor),
handle: "@h1",
wantHandle: "@h1",
wantType: drivertype.TSV,
query: actorDataQuery,
},
{
loc: proj.Abs(sakila.PathTSVActorNoHeader),
handle: "@h1",
wantHandle: "@h1",
wantType: drivertype.TSV,
query: actorDataQuery,
},
{
// sqlite can be added both with and without the scheme "sqlite://"
loc: "sqlite3://" + proj.Abs(sakila.PathSL3),
wantHandle: "@sakila",
wantType: drivertype.SQLite,
},
{
// with scheme
loc: proj.Abs(sakila.PathSL3),
wantHandle: "@sakila",
wantType: drivertype.SQLite,
},
{
// without scheme, relative path
loc: proj.Rel(sakila.PathSL3),
wantHandle: "@sakila",
wantType: drivertype.SQLite,
},
{
locFromHandle: sakila.Pg,
wantHandle: "@sakila",
wantType: drivertype.Pg,
},
{
locFromHandle: sakila.MS,
wantHandle: "@sakila",
wantType: drivertype.MSSQL,
},
{
locFromHandle: sakila.My,
wantHandle: "@sakila",
wantType: drivertype.MySQL,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: source.StdinHandle, // reserved handle
wantAddErr: true,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: source.ActiveHandle, // reserved handle
wantAddErr: true,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: source.ScratchHandle, // reserved handle
wantAddErr: true,
},
{
loc: proj.Abs(sakila.PathCSVActor),
handle: source.JoinHandle, // reserved handle
wantAddErr: true,
},
}
for i, tc := range testCases {
tc := tc
t.Run(tu.Name(i, tc.wantHandle, tc.loc, tc.locFromHandle, tc.driver), func(t *testing.T) {
if tc.locFromHandle != "" {
th := testh.New(t)
tc.loc = th.Source(tc.locFromHandle).Location
}
args := []string{"add", tc.loc}
if tc.handle != "" {
args = append(args, "--handle="+tc.handle)
}
if tc.driver != "" {
args = append(args, "--driver="+tc.driver)
}
tr := testrun.New(context.Background(), t, nil)
err := tr.Exec(args...)
if tc.wantAddErr {
require.Error(t, err)
return
}
require.NoError(t, err)
// Verify that the src was actually added
gotSrc, err := tr.Run.Config.Collection.Get(tc.wantHandle)
require.NoError(t, err)
require.Equal(t, tc.wantHandle, gotSrc.Handle)
require.Equal(t, tc.wantType, gotSrc.Type)
require.Equal(t, len(tc.wantOptions), len(gotSrc.Options))
if tc.query == nil {
return
}
err = tr.Reset().Exec(tc.query.q, "--json")
if tc.wantQueryErr {
require.Error(t, err)
return
}
require.NoError(t, err)
var results []map[string]any
tr.Bind(&results)
require.Equal(t, tc.query.wantRows, len(results))
if tc.query.wantRows > 0 {
require.Equal(t, tc.query.wantCols, len(results[0]))
}
})
}
}
// TestCmdAdd_SQLite_Path has additional tests for sqlite paths.
func TestCmdAdd_SQLite_Path(t *testing.T) {
t.Parallel()
ctx := context.Background()
const h1 = `@s1`
tr := testrun.New(ctx, t, nil)
require.NoError(t, tr.Exec("add", "-j", "sqlite3://test.db", "--handle", h1))
got := tr.BindMap()
absPath, err := filepath.Abs("test.db")
require.NoError(t, err)
absPath = filepath.ToSlash(absPath)
wantLoc := "sqlite3://" + absPath
require.Equal(t, wantLoc, got["location"])
}
func TestCmdAdd_Active(t *testing.T) {
t.Parallel()
const h1, h2, h3, h4 = "@h1", "@h2", "@h3", "@h4"
ctx := context.Background()
// Verify that initially there are no sources.
tr := testrun.New(ctx, t, nil)
require.NoError(t, tr.Exec("ls"))
require.Zero(t, tr.Out.Len())
// Add a new source. It should become the active source.
tr = testrun.New(ctx, t, tr)
require.NoError(t, tr.Exec("add", proj.Abs(sakila.PathCSVActor), "--handle", h1))
tr = testrun.New(ctx, t, tr)
require.NoError(t, tr.Exec("src", "-j"))
m := tr.BindMap()
require.Equal(t, h1, m["handle"])
// Add a second src, without the --active flag. The active src
// should remain h1.
tr = testrun.New(ctx, t, tr)
require.NoError(t, tr.Exec("add", proj.Abs(sakila.PathCSVActor), "--handle", h2))
tr = testrun.New(ctx, t, tr)
require.NoError(t, tr.Exec("src", "-j"))
m = tr.BindMap()
require.Equal(t, h1, m["handle"], "active source should still be %s", h1)
// Add a third src, this time with the --active flag. The active src
// should become h3.
tr = testrun.New(ctx, t, tr)
require.NoError(t, tr.Exec("add", proj.Abs(sakila.PathCSVActor), "--handle", h3, "--active"))
tr = testrun.New(ctx, t, tr)
require.NoError(t, tr.Exec("src", "-j"))
m = tr.BindMap()
require.Equal(t, h3, m["handle"], "active source now be %s", h3)
// Same again with a fourth src, but this time using the shorthand -a flag.
tr = testrun.New(ctx, t, tr)
require.NoError(t, tr.Exec("add", proj.Abs(sakila.PathCSVActor), "--handle", h4, "-a"))
tr = testrun.New(ctx, t, tr)
require.NoError(t, tr.Exec("src", "-j"))
m = tr.BindMap()
require.Equal(t, h4, m["handle"], "active source now be %s", h4)
}