Added set-n command to set number attributes
Also added the ability to set subattribes of maps
This commit is contained in:
parent
ed577dc53e
commit
e35855f05c
64
internal/dynamo-browse/controllers/attrpath.go
Normal file
64
internal/dynamo-browse/controllers/attrpath.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||||
|
"github.com/lmika/awstools/internal/dynamo-browse/models"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attrPath []string
|
||||||
|
|
||||||
|
func newAttrPath(expr string) attrPath {
|
||||||
|
return strings.Split(expr, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap attrPath) follow(item models.Item) (types.AttributeValue, error) {
|
||||||
|
var step types.AttributeValue
|
||||||
|
for i, seg := range ap {
|
||||||
|
if i == 0 {
|
||||||
|
step = item[seg]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s := step.(type) {
|
||||||
|
case *types.AttributeValueMemberM:
|
||||||
|
step = s.Value[seg]
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("seg %v expected to be a map", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return step, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ap attrPath) setAt(item models.Item, newValue types.AttributeValue) error {
|
||||||
|
if len(ap) == 1 {
|
||||||
|
item[ap[0]] = newValue
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var step types.AttributeValue
|
||||||
|
for i, seg := range ap[:len(ap)-1] {
|
||||||
|
if i == 0 {
|
||||||
|
step = item[seg]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s := step.(type) {
|
||||||
|
case *types.AttributeValueMemberM:
|
||||||
|
step = s.Value[seg]
|
||||||
|
default:
|
||||||
|
return errors.Errorf("seg %v expected to be a map", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSeg := ap[len(ap)-1]
|
||||||
|
switch s := step.(type) {
|
||||||
|
case *types.AttributeValueMemberM:
|
||||||
|
s.Value[lastSeg] = newValue
|
||||||
|
default:
|
||||||
|
return errors.Errorf("last seg expected to be a map, but was %T", lastSeg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -37,6 +37,13 @@ func (s *State) withResultSet(rs func(*models.ResultSet)) {
|
||||||
rs(s.resultSet)
|
rs(s.resultSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *State) withResultSetReturningError(rs func(*models.ResultSet) error) (err error) {
|
||||||
|
s.withResultSet(func(set *models.ResultSet) {
|
||||||
|
err = rs(set)
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) setResultSetAndFilter(resultSet *models.ResultSet, filter string) {
|
func (s *State) setResultSetAndFilter(resultSet *models.ResultSet, filter string) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
|
@ -70,7 +70,7 @@ func TestTableReadController_ExportCSV(t *testing.T) {
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
readController := controllers.NewTableReadController(controllers.NewState(), service, "alpha-table")
|
readController := controllers.NewTableReadController(controllers.NewState(), service, "bravo-table")
|
||||||
|
|
||||||
t.Run("should export result set to CSV file", func(t *testing.T) {
|
t.Run("should export result set to CSV file", func(t *testing.T) {
|
||||||
tempFile := tempFile(t)
|
tempFile := tempFile(t)
|
||||||
|
@ -83,9 +83,9 @@ func TestTableReadController_ExportCSV(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, string(bts), strings.Join([]string{
|
assert.Equal(t, string(bts), strings.Join([]string{
|
||||||
"pk,sk,alpha,beta,gamma\n",
|
"pk,sk,alpha,beta,gamma\n",
|
||||||
"abc,111,This is some value,,\n",
|
|
||||||
"abc,222,This is another some value,1231,\n",
|
"abc,222,This is another some value,1231,\n",
|
||||||
"bbb,131,,2468,foobar\n",
|
"bbb,131,,2468,foobar\n",
|
||||||
|
"foo,bar,This is some value,,\n",
|
||||||
}, ""))
|
}, ""))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ func TestTableReadController_Query(t *testing.T) {
|
||||||
|
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
readController := controllers.NewTableReadController(controllers.NewState(), service, "alpha-table")
|
readController := controllers.NewTableReadController(controllers.NewState(), service, "bravo-table")
|
||||||
|
|
||||||
t.Run("should run scan with filter based on user query", func(t *testing.T) {
|
t.Run("should run scan with filter based on user query", func(t *testing.T) {
|
||||||
tempFile := tempFile(t)
|
tempFile := tempFile(t)
|
||||||
|
@ -120,7 +120,6 @@ func TestTableReadController_Query(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, string(bts), strings.Join([]string{
|
assert.Equal(t, string(bts), strings.Join([]string{
|
||||||
"pk,sk,alpha,beta\n",
|
"pk,sk,alpha,beta\n",
|
||||||
"abc,111,This is some value,\n",
|
|
||||||
"abc,222,This is another some value,1231\n",
|
"abc,222,This is another some value,1231\n",
|
||||||
}, ""))
|
}, ""))
|
||||||
})
|
})
|
||||||
|
@ -213,6 +212,11 @@ var testData = []testdynamo.TestData{
|
||||||
"pk": "abc",
|
"pk": "abc",
|
||||||
"sk": "111",
|
"sk": "111",
|
||||||
"alpha": "This is some value",
|
"alpha": "This is some value",
|
||||||
|
"age": 23,
|
||||||
|
"address": map[string]any{
|
||||||
|
"no": 123,
|
||||||
|
"street": "Fake st.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pk": "abc",
|
"pk": "abc",
|
||||||
|
|
|
@ -70,15 +70,67 @@ func (twc *TableWriteController) NewItem() tea.Cmd {
|
||||||
|
|
||||||
func (twc *TableWriteController) SetStringValue(idx int, key string) tea.Cmd {
|
func (twc *TableWriteController) SetStringValue(idx int, key string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
|
// Verify that the expression is valid
|
||||||
|
apPath := newAttrPath(key)
|
||||||
|
|
||||||
|
if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error {
|
||||||
|
_, err := apPath.follow(set.Items()[idx])
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return events.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
return events.PromptForInputMsg{
|
return events.PromptForInputMsg{
|
||||||
Prompt: "string value: ",
|
Prompt: "string value: ",
|
||||||
OnDone: func(value string) tea.Cmd {
|
OnDone: func(value string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
twc.state.withResultSet(func(set *models.ResultSet) {
|
if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error {
|
||||||
set.Items()[idx][key] = &types.AttributeValueMemberS{Value: value}
|
err := apPath.setAt(set.Items()[idx], &types.AttributeValueMemberS{Value: value})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
set.SetDirty(idx, true)
|
set.SetDirty(idx, true)
|
||||||
set.RefreshColumns()
|
set.RefreshColumns()
|
||||||
})
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return events.Error(err)
|
||||||
|
}
|
||||||
|
return ResultSetUpdated{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (twc *TableWriteController) SetNumberValue(idx int, key string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
// Verify that the expression is valid
|
||||||
|
apPath := newAttrPath(key)
|
||||||
|
|
||||||
|
if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error {
|
||||||
|
_, err := apPath.follow(set.Items()[idx])
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return events.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return events.PromptForInputMsg{
|
||||||
|
Prompt: "number value: ",
|
||||||
|
OnDone: func(value string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error {
|
||||||
|
err := apPath.setAt(set.Items()[idx], &types.AttributeValueMemberN{Value: value})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
set.SetDirty(idx, true)
|
||||||
|
set.RefreshColumns()
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return events.Error(err)
|
||||||
|
}
|
||||||
return ResultSetUpdated{}
|
return ResultSetUpdated{}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package controllers_test
|
package controllers_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||||
"github.com/lmika/awstools/internal/dynamo-browse/controllers"
|
"github.com/lmika/awstools/internal/dynamo-browse/controllers"
|
||||||
"github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo"
|
"github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo"
|
||||||
"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
|
"github.com/lmika/awstools/internal/dynamo-browse/services/tables"
|
||||||
|
@ -9,177 +10,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTableWriteController_ToggleReadWrite(t *testing.T) {
|
|
||||||
t.Skip("needs to be updated")
|
|
||||||
|
|
||||||
/*
|
|
||||||
twc, _, closeFn := setupController(t)
|
|
||||||
t.Cleanup(closeFn)
|
|
||||||
|
|
||||||
t.Run("should enabling read write if disabled", func(t *testing.T) {
|
|
||||||
ctx, uiCtx := testuictx.New(context.Background())
|
|
||||||
ctx = controllers.ContextWithState(ctx, controllers.State{
|
|
||||||
InReadWriteMode: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
err := twc.ToggleReadWrite().Execute(ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Contains(t, uiCtx.Messages, controllers.SetReadWrite{NewValue: true})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should disable read write if enabled", func(t *testing.T) {
|
|
||||||
ctx, uiCtx := testuictx.New(context.Background())
|
|
||||||
ctx = controllers.ContextWithState(ctx, controllers.State{
|
|
||||||
InReadWriteMode: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
err := twc.ToggleReadWrite().Execute(ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Contains(t, uiCtx.Messages, controllers.SetReadWrite{NewValue: false})
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTableWriteController_Delete(t *testing.T) {
|
|
||||||
/*
|
|
||||||
t.Run("should delete selected item if in read/write mode is inactive", func(t *testing.T) {
|
|
||||||
twc, ctrls, closeFn := setupController(t)
|
|
||||||
t.Cleanup(closeFn)
|
|
||||||
|
|
||||||
ti, err := ctrls.tableService.Describe(context.Background(), ctrls.tableName)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
resultSet, err := ctrls.tableService.Scan(context.Background(), ti)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, resultSet.Items, 3)
|
|
||||||
|
|
||||||
ctx, uiCtx := testuictx.New(context.Background())
|
|
||||||
ctx = controllers.ContextWithState(ctx, controllers.State{
|
|
||||||
ResultSet: resultSet,
|
|
||||||
SelectedItem: resultSet.Items[1],
|
|
||||||
InReadWriteMode: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
op := twc.Delete()
|
|
||||||
|
|
||||||
// Should prompt first
|
|
||||||
err = op.Execute(ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
_ = uiCtx
|
|
||||||
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
promptRequest, ok := uiCtx.Messages[0].(events.PromptForInput)
|
|
||||||
assert.True(t, ok)
|
|
||||||
|
|
||||||
// After prompt, continue to delete
|
|
||||||
err = promptRequest.OnDone.Execute(uimodels.WithPromptValue(ctx, "y"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
afterResultSet, err := ctrls.tableService.Scan(context.Background(), ti)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, afterResultSet.Items, 2)
|
|
||||||
assert.Contains(t, afterResultSet.Items, resultSet.Items[0])
|
|
||||||
assert.NotContains(t, afterResultSet.Items, resultSet.Items[1])
|
|
||||||
assert.Contains(t, afterResultSet.Items, resultSet.Items[2])
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should not delete selected item if prompt is not y", func(t *testing.T) {
|
|
||||||
twc, ctrls, closeFn := setupController(t)
|
|
||||||
t.Cleanup(closeFn)
|
|
||||||
|
|
||||||
ti, err := ctrls.tableService.Describe(context.Background(), ctrls.tableName)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
resultSet, err := ctrls.tableService.Scan(context.Background(), ti)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, resultSet.Items, 3)
|
|
||||||
|
|
||||||
ctx, uiCtx := testuictx.New(context.Background())
|
|
||||||
ctx = controllers.ContextWithState(ctx, controllers.State{
|
|
||||||
ResultSet: resultSet,
|
|
||||||
SelectedItem: resultSet.Items[1],
|
|
||||||
InReadWriteMode: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
op := twc.Delete()
|
|
||||||
|
|
||||||
// Should prompt first
|
|
||||||
err = op.Execute(ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
_ = uiCtx
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
promptRequest, ok := uiCtx.Messages[0].(events.PromptForInput)
|
|
||||||
assert.True(t, ok)
|
|
||||||
|
|
||||||
// After prompt, continue to delete
|
|
||||||
err = promptRequest.OnDone.Execute(uimodels.WithPromptValue(ctx, "n"))
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
afterResultSet, err := ctrls.tableService.Scan(context.Background(), ti)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, afterResultSet.Items, 3)
|
|
||||||
assert.Contains(t, afterResultSet.Items, resultSet.Items[0])
|
|
||||||
assert.Contains(t, afterResultSet.Items, resultSet.Items[1])
|
|
||||||
assert.Contains(t, afterResultSet.Items, resultSet.Items[2])
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should not delete if read/write mode is inactive", func(t *testing.T) {
|
|
||||||
tableWriteController, ctrls, closeFn := setupController(t)
|
|
||||||
t.Cleanup(closeFn)
|
|
||||||
|
|
||||||
ti, err := ctrls.tableService.Describe(context.Background(), ctrls.tableName)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
resultSet, err := ctrls.tableService.Scan(context.Background(), ti)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, resultSet.Items, 3)
|
|
||||||
|
|
||||||
ctx, _ := testuictx.New(context.Background())
|
|
||||||
ctx = controllers.ContextWithState(ctx, controllers.State{
|
|
||||||
ResultSet: resultSet,
|
|
||||||
SelectedItem: resultSet.Items[1],
|
|
||||||
InReadWriteMode: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
op := tableWriteController.Delete()
|
|
||||||
|
|
||||||
err = op.Execute(ctx)
|
|
||||||
assert.Error(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
type controller struct {
|
|
||||||
tableName string
|
|
||||||
tableService *tables.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupController(t *testing.T) (*controllers.TableWriteController, controller, func()) {
|
|
||||||
tableName := "table-write-controller-table"
|
|
||||||
|
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, tableName, testData)
|
|
||||||
provider := dynamo.NewProvider(client)
|
|
||||||
tableService := tables.NewService(provider)
|
|
||||||
tableReadController := controllers.NewTableReadController(tableService, tableName)
|
|
||||||
tableWriteController := controllers.NewTableWriteController(tableService, tableReadController)
|
|
||||||
return tableWriteController, controller{
|
|
||||||
tableName: tableName,
|
|
||||||
tableService: tableService,
|
|
||||||
}, cleanupFn
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func TestTableWriteController_NewItem(t *testing.T) {
|
func TestTableWriteController_NewItem(t *testing.T) {
|
||||||
t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) {
|
t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) {
|
||||||
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||||
|
@ -218,7 +48,7 @@ func TestTableWriteController_SetStringValue(t *testing.T) {
|
||||||
provider := dynamo.NewProvider(client)
|
provider := dynamo.NewProvider(client)
|
||||||
service := tables.NewService(provider)
|
service := tables.NewService(provider)
|
||||||
|
|
||||||
t.Run("should add a new empty item at the end of the result set", func(t *testing.T) {
|
t.Run("should change the value of a string field if already present", func(t *testing.T) {
|
||||||
state := controllers.NewState()
|
state := controllers.NewState()
|
||||||
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
||||||
writeController := controllers.NewTableWriteController(state, service, readController)
|
writeController := controllers.NewTableWriteController(state, service, readController)
|
||||||
|
@ -235,8 +65,73 @@ func TestTableWriteController_SetStringValue(t *testing.T) {
|
||||||
assert.True(t, state.ResultSet().IsDirty(0))
|
assert.True(t, state.ResultSet().IsDirty(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should prevent duplicate partition,sort keys", func(t *testing.T) {
|
t.Run("should change the value of a string field within a map if already present", func(t *testing.T) {
|
||||||
t.Skip("TODO")
|
state := controllers.NewState()
|
||||||
|
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
||||||
|
writeController := controllers.NewTableWriteController(state, service, readController)
|
||||||
|
|
||||||
|
invokeCommand(t, readController.Init())
|
||||||
|
|
||||||
|
beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
|
||||||
|
beforeStreet := beforeAddress.Value["street"].(*types.AttributeValueMemberS).Value
|
||||||
|
|
||||||
|
assert.Equal(t, "Fake st.", beforeStreet)
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(0))
|
||||||
|
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetStringValue(0, "address.street"), "Fiction rd.")
|
||||||
|
|
||||||
|
afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
|
||||||
|
afterStreet := afterAddress.Value["street"].(*types.AttributeValueMemberS).Value
|
||||||
|
|
||||||
|
assert.Equal(t, "Fiction rd.", afterStreet)
|
||||||
|
assert.True(t, state.ResultSet().IsDirty(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableWriteController_SetNumberValue(t *testing.T) {
|
||||||
|
client, cleanupFn := testdynamo.SetupTestTable(t, testData)
|
||||||
|
defer cleanupFn()
|
||||||
|
|
||||||
|
provider := dynamo.NewProvider(client)
|
||||||
|
service := tables.NewService(provider)
|
||||||
|
|
||||||
|
t.Run("should change the value of a number field if already present", func(t *testing.T) {
|
||||||
|
state := controllers.NewState()
|
||||||
|
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
||||||
|
writeController := controllers.NewTableWriteController(state, service, readController)
|
||||||
|
|
||||||
|
invokeCommand(t, readController.Init())
|
||||||
|
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("age")
|
||||||
|
assert.Equal(t, "23", before)
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(0))
|
||||||
|
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetNumberValue(0, "age"), "46")
|
||||||
|
|
||||||
|
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("age")
|
||||||
|
assert.Equal(t, "46", after)
|
||||||
|
assert.True(t, state.ResultSet().IsDirty(0))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should change the value of a number field within a map if already present", func(t *testing.T) {
|
||||||
|
state := controllers.NewState()
|
||||||
|
readController := controllers.NewTableReadController(state, service, "alpha-table")
|
||||||
|
writeController := controllers.NewTableWriteController(state, service, readController)
|
||||||
|
|
||||||
|
invokeCommand(t, readController.Init())
|
||||||
|
|
||||||
|
beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
|
||||||
|
beforeStreet := beforeAddress.Value["no"].(*types.AttributeValueMemberN).Value
|
||||||
|
|
||||||
|
assert.Equal(t, "123", beforeStreet)
|
||||||
|
assert.False(t, state.ResultSet().IsDirty(0))
|
||||||
|
|
||||||
|
invokeCommandWithPrompt(t, writeController.SetNumberValue(0, "address.no"), "456")
|
||||||
|
|
||||||
|
afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
|
||||||
|
afterStreet := afterAddress.Value["no"].(*types.AttributeValueMemberN).Value
|
||||||
|
|
||||||
|
assert.Equal(t, "456", afterStreet)
|
||||||
|
assert.True(t, state.ResultSet().IsDirty(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ func TestModExpr_Query(t *testing.T) {
|
||||||
modExpr, err := queryexpr.Parse(`pk="prefix"`)
|
modExpr, err := queryexpr.Parse(`pk="prefix"`)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
plan, err := modExpr.BuildQuery(tableInfo)
|
plan, err := modExpr.Plan(tableInfo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, plan.CanQuery)
|
assert.False(t, plan.CanQuery)
|
||||||
|
@ -36,7 +36,7 @@ func TestModExpr_Query(t *testing.T) {
|
||||||
modExpr, err := queryexpr.Parse(`pk^="prefix"`) // TODO: fix this so that '^ =' is invalid
|
modExpr, err := queryexpr.Parse(`pk^="prefix"`) // TODO: fix this so that '^ =' is invalid
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
plan, err := modExpr.BuildQuery(tableInfo)
|
plan, err := modExpr.Plan(tableInfo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, plan.CanQuery)
|
assert.False(t, plan.CanQuery)
|
||||||
|
|
|
@ -20,7 +20,7 @@ func TestProvider_ScanItems(t *testing.T) {
|
||||||
t.Run("should return scanned items from the table", func(t *testing.T) {
|
t.Run("should return scanned items from the table", func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
items, err := provider.ScanItems(ctx, tableName, 100)
|
items, err := provider.ScanItems(ctx, tableName, nil, 100)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, items, 3)
|
assert.Len(t, items, 3)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func TestProvider_ScanItems(t *testing.T) {
|
||||||
t.Run("should return error if table name does not exist", func(t *testing.T) {
|
t.Run("should return error if table name does not exist", func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
items, err := provider.ScanItems(ctx, "does-not-exist", 100)
|
items, err := provider.ScanItems(ctx, "does-not-exist", nil, 100)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, items)
|
assert.Nil(t, items)
|
||||||
})
|
})
|
||||||
|
@ -53,7 +53,7 @@ func TestProvider_DeleteItem(t *testing.T) {
|
||||||
"sk": &types.AttributeValueMemberS{Value: "222"},
|
"sk": &types.AttributeValueMemberS{Value: "222"},
|
||||||
})
|
})
|
||||||
|
|
||||||
items, err := provider.ScanItems(ctx, tableName, 100)
|
items, err := provider.ScanItems(ctx, tableName, nil, 100)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, items, 2)
|
assert.Len(t, items, 2)
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ func TestProvider_DeleteItem(t *testing.T) {
|
||||||
"sk": &types.AttributeValueMemberS{Value: "999"},
|
"sk": &types.AttributeValueMemberS{Value: "999"},
|
||||||
})
|
})
|
||||||
|
|
||||||
items, err := provider.ScanItems(ctx, tableName, 100)
|
items, err := provider.ScanItems(ctx, tableName, nil, 100)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, items, 3)
|
assert.Len(t, items, 3)
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ func TestProvider_DeleteItem(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
items, err := provider.ScanItems(ctx, "does-not-exist", 100)
|
items, err := provider.ScanItems(ctx, "does-not-exist", nil, 100)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, items)
|
assert.Nil(t, items)
|
||||||
})
|
})
|
||||||
|
|
|
@ -51,7 +51,7 @@ func TestService_Scan(t *testing.T) {
|
||||||
|
|
||||||
// Hash first, then range, then columns in alphabetic order
|
// Hash first, then range, then columns in alphabetic order
|
||||||
assert.Equal(t, rs.TableInfo, ti)
|
assert.Equal(t, rs.TableInfo, ti)
|
||||||
assert.Equal(t, rs.Columns, []string{"pk", "sk", "alpha", "beta", "gamma"})
|
assert.Equal(t, rs.Columns(), []string{"pk", "sk", "alpha", "beta", "gamma"})
|
||||||
//assert.Equal(t, rs.Items[0], testdynamo.TestRecordAsItem(t, testData[1]))
|
//assert.Equal(t, rs.Items[0], testdynamo.TestRecordAsItem(t, testData[1]))
|
||||||
//assert.Equal(t, rs.Items[1], testdynamo.TestRecordAsItem(t, testData[0]))
|
//assert.Equal(t, rs.Items[1], testdynamo.TestRecordAsItem(t, testData[0]))
|
||||||
//assert.Equal(t, rs.Items[2], testdynamo.TestRecordAsItem(t, testData[2]))
|
//assert.Equal(t, rs.Items[2], testdynamo.TestRecordAsItem(t, testData[2]))
|
||||||
|
|
|
@ -62,6 +62,12 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon
|
||||||
}
|
}
|
||||||
return wc.SetStringValue(dtv.SelectedItemIndex(), args[0])
|
return wc.SetStringValue(dtv.SelectedItemIndex(), args[0])
|
||||||
},
|
},
|
||||||
|
"set-n": func(args []string) tea.Cmd {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return events.SetError(errors.New("expected field"))
|
||||||
|
}
|
||||||
|
return wc.SetNumberValue(dtv.SelectedItemIndex(), args[0])
|
||||||
|
},
|
||||||
|
|
||||||
"put": func(args []string) tea.Cmd {
|
"put": func(args []string) tea.Cmd {
|
||||||
return wc.PutItem(dtv.SelectedItemIndex())
|
return wc.PutItem(dtv.SelectedItemIndex())
|
||||||
|
@ -75,8 +81,7 @@ func NewModel(rc *controllers.TableReadController, wc *controllers.TableWriteCon
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
//root := layout.FullScreen(tableSelect)
|
root := layout.FullScreen(tableSelect)
|
||||||
root := layout.FullScreen(dialogPrompt)
|
|
||||||
|
|
||||||
return Model{
|
return Model{
|
||||||
tableReadController: rc,
|
tableReadController: rc,
|
||||||
|
|
|
@ -14,7 +14,7 @@ func New(model layout.ResizingModel) *Model {
|
||||||
compositor: layout.NewCompositor(model),
|
compositor: layout.NewCompositor(model),
|
||||||
}
|
}
|
||||||
// TEMP
|
// TEMP
|
||||||
m.compositor.SetOverlay(&dialogModel{}, 5, 5, 30, 12)
|
//m.compositor.SetOverlay(&dialogModel{}, 5, 5, 30, 12)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,11 +60,13 @@ func SetupTestTable(t *testing.T, testData []TestData) (*dynamodb.Client, func()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dynamoClient, func() {
|
t.Cleanup(func() {
|
||||||
for _, table := range testData {
|
for _, table := range testData {
|
||||||
dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||||
TableName: aws.String(table.TableName),
|
TableName: aws.String(table.TableName),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
return dynamoClient, func() {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue