Issue 24: Added read-only mode (#27)

- Added settings to workspace, and added the read-only mode
- Added the `-ro` field which will launch Dynamo-Browse in read-only mode
- Added the `set ro` to enable read-only mode, and `set rw` to enable read-write mode
This commit is contained in:
Leon Mika 2022-09-29 22:10:18 +10:00 committed by GitHub
parent a1717572c5
commit 93ec519127
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 623 additions and 315 deletions

View file

@ -14,6 +14,7 @@ import (
"github.com/lmika/audax/internal/common/workspaces"
"github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
"github.com/lmika/audax/internal/dynamo-browse/providers/settingstore"
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
keybindings_service "github.com/lmika/audax/internal/dynamo-browse/services/keybindings"
@ -32,6 +33,7 @@ func main() {
var flagTable = flag.String("t", "", "dynamodb table name")
var flagLocal = flag.String("local", "", "local endpoint")
var flagDebug = flag.String("debug", "", "file to log debug messages")
var flagRO = flag.Bool("ro", false, "enable readonly mode")
var flagWorkspace = flag.String("w", "", "workspace file")
flag.Parse()
@ -73,14 +75,22 @@ func main() {
uiStyles := styles.DefaultStyles
dynamoProvider := dynamo.NewProvider(dynamoClient)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
settingStore := settingstore.New(ws)
tableService := tables.NewService(dynamoProvider)
if *flagRO {
if err := settingStore.SetReadOnly(*flagRO); err != nil {
cli.Fatalf("unable to set read-only mode: %v", err)
}
}
tableService := tables.NewService(dynamoProvider, settingStore)
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
state := controllers.NewState()
tableReadController := controllers.NewTableReadController(state, tableService, workspaceService, itemRendererService, *flagTable, true)
tableWriteController := controllers.NewTableWriteController(state, tableService, tableReadController)
tableWriteController := controllers.NewTableWriteController(state, tableService, tableReadController, settingStore)
settingsController := controllers.NewSettingsController(settingStore)
keyBindings := keybindings.Default()
keyBindingService := keybindings_service.NewService(keyBindings)
@ -91,6 +101,7 @@ func main() {
model := ui.NewModel(
tableReadController,
tableWriteController,
settingsController,
itemRendererService,
commandController,
keyBindingController,

View file

@ -10,6 +10,11 @@ type ErrorMsg error
// Message indicates that a message should be shown to the user
type StatusMsg string
type WrappedStatusMsg struct {
Message StatusMsg
Next tea.Msg
}
// ModeMessage indicates that the mode should be changed to the following
type ModeMessage string

View file

@ -10,6 +10,9 @@ type SetTableItemView struct {
ViewIndex int
}
type SettingsUpdated struct {
}
type NewResultSet struct {
ResultSet *models.ResultSet
currentFilter string
@ -44,10 +47,6 @@ func (rs NewResultSet) StatusMessage() string {
}
}
type SetReadWrite struct {
NewValue bool
}
type PromptForTableMsg struct {
Tables []string
OnSelected func(tableName string) tea.Msg

View file

@ -12,3 +12,8 @@ type TableReadService interface {
Filter(resultSet *models.ResultSet, filter string) *models.ResultSet
ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo, query models.Queryable) (*models.ResultSet, error)
}
type SettingsProvider interface {
IsReadOnly() (bool, error)
SetReadOnly(ro bool) error
}

View file

@ -0,0 +1,49 @@
package controllers
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/audax/internal/common/ui/events"
"github.com/pkg/errors"
"log"
)
type SettingsController struct {
settings SettingsProvider
}
func NewSettingsController(sp SettingsProvider) *SettingsController {
return &SettingsController{
settings: sp,
}
}
func (sc *SettingsController) SetSetting(name string, value string) tea.Msg {
switch name {
case "ro":
if err := sc.settings.SetReadOnly(true); err != nil {
return events.Error(err)
}
return events.WrappedStatusMsg{
Message: "In read-only mode",
Next: SettingsUpdated{},
}
case "rw":
if err := sc.settings.SetReadOnly(false); err != nil {
return events.Error(err)
}
return events.WrappedStatusMsg{
Message: "In read-write mode",
Next: SettingsUpdated{},
}
}
return events.Error(errors.Errorf("unrecognised setting: %v", name))
}
func (sc *SettingsController) IsReadOnly() bool {
ro, err := sc.settings.IsReadOnly()
if err != nil {
log.Printf("warn: unable to determine if R/O is available: %v", err)
return false
}
return ro
}

View file

@ -0,0 +1,30 @@
package controllers_test
import (
"github.com/lmika/audax/internal/common/ui/events"
"github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSettingsController_SetSetting(t *testing.T) {
t.Run("read-only setting", func(t *testing.T) {
srv := newService(t, false)
msg := invokeCommand(t, srv.settingsController.SetSetting("ro", ""))
assert.True(t, srv.settingsController.IsReadOnly())
assert.IsType(t, events.WrappedStatusMsg{}, msg)
assert.IsType(t, controllers.SettingsUpdated{}, msg.(events.WrappedStatusMsg).Next)
})
t.Run("read-write setting", func(t *testing.T) {
srv := newService(t, true)
msg := invokeCommand(t, srv.settingsController.SetSetting("rw", ""))
assert.False(t, srv.settingsController.IsReadOnly())
assert.IsType(t, events.WrappedStatusMsg{}, msg)
assert.IsType(t, controllers.SettingsUpdated{}, msg.(events.WrappedStatusMsg).Next)
})
}

View file

@ -26,7 +26,7 @@ func TestTableReadController_InitTable(t *testing.T) {
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
service := tables.NewService(provider, &mockedSetting{})
t.Run("should prompt for table if no table name provided", func(t *testing.T) {
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "", false)
@ -53,7 +53,7 @@ func TestTableReadController_ListTables(t *testing.T) {
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
service := tables.NewService(provider, &mockedSetting{})
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "", false)
t.Run("returns a list of tables", func(t *testing.T) {
@ -78,7 +78,7 @@ func TestTableReadController_Rescan(t *testing.T) {
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
service := tables.NewService(provider, &mockedSetting{})
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "bravo-table", false)
@ -116,7 +116,7 @@ func TestTableReadController_ExportCSV(t *testing.T) {
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
service := tables.NewService(provider, &mockedSetting{})
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "bravo-table", false)
t.Run("should export result set to CSV file", func(t *testing.T) {
@ -155,7 +155,7 @@ func TestTableReadController_Query(t *testing.T) {
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
service := tables.NewService(provider, &mockedSetting{})
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "bravo-table", false)
t.Run("should run scan with filter based on user query", func(t *testing.T) {

View file

@ -17,13 +17,15 @@ type TableWriteController struct {
state *State
tableService *tables.Service
tableReadControllers *TableReadController
settingProvider SettingsProvider
}
func NewTableWriteController(state *State, tableService *tables.Service, tableReadControllers *TableReadController) *TableWriteController {
func NewTableWriteController(state *State, tableService *tables.Service, tableReadControllers *TableReadController, settingProvider SettingsProvider) *TableWriteController {
return &TableWriteController{
state: state,
tableService: tableService,
tableReadControllers: tableReadControllers,
settingProvider: settingProvider,
}
}
@ -36,6 +38,10 @@ func (twc *TableWriteController) ToggleMark(idx int) tea.Msg {
}
func (twc *TableWriteController) NewItem() tea.Msg {
if err := twc.assertReadWrite(); err != nil {
return events.Error(err)
}
// Work out which keys we need to prompt for
rs := twc.state.ResultSet()
@ -226,6 +232,10 @@ func (twc *TableWriteController) DeleteAttribute(idx int, key string) tea.Msg {
}
func (twc *TableWriteController) PutItem(idx int) tea.Msg {
if err := twc.assertReadWrite(); err != nil {
return events.Error(err)
}
resultSet := twc.state.ResultSet()
if !resultSet.IsDirty(idx) {
return events.Error(errors.New("item is not dirty"))
@ -247,6 +257,10 @@ func (twc *TableWriteController) PutItem(idx int) tea.Msg {
}
func (twc *TableWriteController) PutItems() tea.Msg {
if err := twc.assertReadWrite(); err != nil {
return events.Error(err)
}
var (
markedItemCount int
)
@ -309,6 +323,10 @@ func (twc *TableWriteController) PutItems() tea.Msg {
}
func (twc *TableWriteController) TouchItem(idx int) tea.Msg {
if err := twc.assertReadWrite(); err != nil {
return events.Error(err)
}
resultSet := twc.state.ResultSet()
if resultSet.IsDirty(idx) {
return events.Error(errors.New("cannot touch dirty items"))
@ -330,6 +348,10 @@ func (twc *TableWriteController) TouchItem(idx int) tea.Msg {
}
func (twc *TableWriteController) NoisyTouchItem(idx int) tea.Msg {
if err := twc.assertReadWrite(); err != nil {
return events.Error(err)
}
resultSet := twc.state.ResultSet()
if resultSet.IsDirty(idx) {
return events.Error(errors.New("cannot noisy touch dirty items"))
@ -359,6 +381,10 @@ func (twc *TableWriteController) NoisyTouchItem(idx int) tea.Msg {
}
func (twc *TableWriteController) DeleteMarked() tea.Msg {
if err := twc.assertReadWrite(); err != nil {
return events.Error(err)
}
resultSet := twc.state.ResultSet()
markedItems := resultSet.MarkedItems()
@ -385,6 +411,16 @@ func (twc *TableWriteController) DeleteMarked() tea.Msg {
}
}
func (twc *TableWriteController) assertReadWrite() error {
b, err := twc.settingProvider.IsReadOnly()
if err != nil {
return err
} else if b {
return models.ErrReadOnly
}
return nil
}
func applyToN(prefix string, n int, singular, plural, suffix string) string {
if n == 1 {
return fmt.Sprintf("%v%v %v%v", prefix, n, singular, suffix)

View file

@ -16,27 +16,16 @@ import (
)
func TestTableWriteController_NewItem(t *testing.T) {
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
t.Run("should add an item with pk and sk set at the end of the result set", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
srv := newService(t, false)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
assert.Len(t, state.ResultSet().Items(), 3)
invokeCommand(t, srv.readController.Init())
assert.Len(t, srv.state.ResultSet().Items(), 3)
// Prompt for keys
invokeCommandWithPrompts(t, writeController.NewItem(), "pk-value", "sk-value")
invokeCommandWithPrompts(t, srv.writeController.NewItem(), "pk-value", "sk-value")
newResultSet := state.ResultSet()
newResultSet := srv.state.ResultSet()
assert.Len(t, newResultSet.Items(), 4)
assert.Len(t, newResultSet.Items()[3], 2)
@ -47,13 +36,25 @@ func TestTableWriteController_NewItem(t *testing.T) {
assert.True(t, newResultSet.IsNew(3))
assert.True(t, newResultSet.IsDirty(3))
})
t.Run("should do nothing when in read-only mode", func(t *testing.T) {
srv := newService(t, true)
invokeCommand(t, srv.readController.Init())
assert.Len(t, srv.state.ResultSet().Items(), 3)
// Prompt for keys
invokeCommandExpectingError(t, srv.writeController.NewItem())
// Confirm no changes
invokeCommand(t, srv.readController.Rescan())
newResultSet := srv.state.ResultSet()
assert.Len(t, newResultSet.Items(), 3)
})
}
func TestTableWriteController_SetAttributeValue(t *testing.T) {
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
t.Run("should preserve the type of the field if unspecified", func(t *testing.T) {
scenarios := []struct {
@ -85,51 +86,32 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
for _, scenario := range scenarios {
t.Run(fmt.Sprintf("should set value of field: %v", scenario.attrKey), func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
srv := newService(t, false)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
invokeCommand(t, srv.readController.Init())
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.UnsetItemType, scenario.attrKey), scenario.attrValue)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.UnsetItemType, scenario.attrKey), scenario.attrValue)
after, _ := state.ResultSet().Items()[0][scenario.attrKey]
after, _ := srv.state.ResultSet().Items()[0][scenario.attrKey]
assert.Equal(t, scenario.expected, after)
assert.True(t, state.ResultSet().IsDirty(0))
assert.True(t, srv.state.ResultSet().IsDirty(0))
})
}
})
t.Run("should use type of selected item for marked fields if unspecified", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
srv := newService(t, false)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
invokeCommand(t, srv.readController.Init())
invokeCommand(t, srv.writeController.ToggleMark(0))
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(1, models.UnsetItemType, "alpha"), "a brand new value")
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
invokeCommand(t, writeController.ToggleMark(0))
invokeCommandWithPrompt(t, writeController.SetAttributeValue(1, models.UnsetItemType, "alpha"), "a brand new value")
after1 := state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value
after1 := srv.state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value
assert.Equal(t, "a brand new value", after1)
assert.True(t, state.ResultSet().IsDirty(0))
assert.False(t, state.ResultSet().IsDirty(1))
assert.True(t, srv.state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(1))
})
t.Run("should change the value to a particular field if already present", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
scenarios := []struct {
attrType models.ItemType
attrValue string
@ -164,97 +146,80 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
for _, scenario := range scenarios {
t.Run(fmt.Sprintf("should change the value of a field to type %v", scenario.attrType), func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
if scenario.attrValue == "" {
invokeCommand(t, writeController.SetAttributeValue(0, scenario.attrType, "alpha"))
invokeCommand(t, srv.writeController.SetAttributeValue(0, scenario.attrType, "alpha"))
} else {
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, scenario.attrType, "alpha"), scenario.attrValue)
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, scenario.attrType, "alpha"), scenario.attrValue)
}
after, _ := state.ResultSet().Items()[0]["alpha"]
after, _ := srv.state.ResultSet().Items()[0]["alpha"]
assert.Equal(t, scenario.expected, after)
assert.True(t, state.ResultSet().IsDirty(0))
assert.True(t, srv.state.ResultSet().IsDirty(0))
})
t.Run(fmt.Sprintf("should change value of nested field to type %v", scenario.attrType), func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
invokeCommand(t, readController.Init())
invokeCommand(t, srv.readController.Init())
beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
beforeAddress := srv.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))
assert.False(t, srv.state.ResultSet().IsDirty(0))
if scenario.attrValue == "" {
invokeCommand(t, writeController.SetAttributeValue(0, scenario.attrType, "address.street"))
invokeCommand(t, srv.writeController.SetAttributeValue(0, scenario.attrType, "address.street"))
} else {
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, scenario.attrType, "address.street"), scenario.attrValue)
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, scenario.attrType, "address.street"), scenario.attrValue)
}
afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
afterAddress := srv.state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
after := afterAddress.Value["street"]
assert.Equal(t, scenario.expected, after)
assert.True(t, state.ResultSet().IsDirty(0))
assert.True(t, srv.state.ResultSet().IsDirty(0))
})
}
})
}
func TestTableWriteController_DeleteAttribute(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
t.Run("should delete top level attribute", func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("age")
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("age")
assert.Equal(t, "23", before)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
invokeCommand(t, writeController.DeleteAttribute(0, "age"))
invokeCommand(t, srv.writeController.DeleteAttribute(0, "age"))
_, hasAge := state.ResultSet().Items()[0]["age"]
_, hasAge := srv.state.ResultSet().Items()[0]["age"]
assert.False(t, hasAge)
})
t.Run("should delete attribute of map", func(t *testing.T) {
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
invokeCommand(t, readController.Init())
invokeCommand(t, srv.readController.Init())
beforeAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
beforeAddress := srv.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))
assert.False(t, srv.state.ResultSet().IsDirty(0))
invokeCommand(t, writeController.DeleteAttribute(0, "address.no"))
invokeCommand(t, srv.writeController.DeleteAttribute(0, "address.no"))
afterAddress := state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
afterAddress := srv.state.ResultSet().Items()[0]["address"].(*types.AttributeValueMemberM)
_, hasStreet := afterAddress.Value["no"]
assert.False(t, hasStreet)
@ -262,296 +227,383 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) {
}
func TestTableWriteController_PutItem(t *testing.T) {
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
t.Run("should put the selected item if dirty", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
// Read the table
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.PutItem(0), "y")
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, srv.writeController.PutItem(0), "y")
// Rescan the table
invokeCommand(t, readController.Rescan())
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommand(t, srv.readController.Rescan())
after, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "a new value", after)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if user does not confirm", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
// Read the table
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item but do not put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.PutItem(0), "n")
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, srv.writeController.PutItem(0), "n")
current, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
current, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "a new value", current)
assert.True(t, state.ResultSet().IsDirty(0))
assert.True(t, srv.state.ResultSet().IsDirty(0))
// Rescan the table to confirm item is not modified
invokeCommandWithPrompt(t, readController.Rescan(), "y")
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommandWithPrompt(t, srv.readController.Rescan(), "y")
after, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if not dirty", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
// Read the table
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
invokeCommandExpectingError(t, writeController.PutItem(0))
invokeCommandExpectingError(t, srv.writeController.PutItem(0))
})
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
// Read the table
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item but do not put it
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandExpectingError(t, srv.writeController.PutItem(0))
current, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "a new value", current)
assert.True(t, srv.state.ResultSet().IsDirty(0))
// Rescan the table to confirm item is not modified
invokeCommandWithPrompt(t, srv.readController.Rescan(), "y")
after, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, srv.state.ResultSet().IsDirty(0))
})
}
func TestTableWriteController_PutItems(t *testing.T) {
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
t.Run("should put all dirty items if none are marked", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
srv := newService(t, false)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
invokeCommand(t, srv.readController.Init())
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommandWithPrompt(t, writeController.PutItems(), "y")
invokeCommandWithPrompt(t, srv.writeController.PutItems(), "y")
// Rescan the table
invokeCommand(t, readController.Rescan())
invokeCommand(t, srv.readController.Rescan())
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "a new value", srv.state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", srv.state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, state.ResultSet().IsDirty(2))
assert.False(t, srv.state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(2))
})
t.Run("only put marked items", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
srv := newService(t, false)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
invokeCommand(t, srv.readController.Init())
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommand(t, writeController.ToggleMark(0))
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommand(t, srv.writeController.ToggleMark(0))
invokeCommandWithPrompt(t, writeController.PutItems(), "y")
invokeCommandWithPrompt(t, srv.writeController.PutItems(), "y")
// Verify dirty items are unchanged
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "a new value", srv.state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", srv.state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.False(t, state.ResultSet().IsDirty(0))
assert.True(t, state.ResultSet().IsDirty(2))
assert.False(t, srv.state.ResultSet().IsDirty(0))
assert.True(t, srv.state.ResultSet().IsDirty(2))
// Rescan the table and verify dirty items were not written
invokeCommandWithPrompt(t, readController.Rescan(), "y")
invokeCommandWithPrompt(t, srv.readController.Rescan(), "y")
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Nil(t, state.ResultSet().Items()[2]["alpha"])
assert.Equal(t, "a new value", srv.state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Nil(t, srv.state.ResultSet().Items()[2]["alpha"])
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, state.ResultSet().IsDirty(2))
assert.False(t, srv.state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(2))
})
t.Run("do not put marked items which are not diry", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
t.Run("do not put marked items which are not dirty", func(t *testing.T) {
srv := newService(t, false)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
invokeCommand(t, readController.Init())
invokeCommand(t, srv.readController.Init())
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommand(t, writeController.ToggleMark(1))
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommand(t, srv.writeController.ToggleMark(1))
invokeCommand(t, writeController.PutItems())
invokeCommand(t, srv.writeController.PutItems())
// Verify dirty items are unchanged
assert.Equal(t, "a new value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "a new value", srv.state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", srv.state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.True(t, state.ResultSet().IsDirty(0))
assert.True(t, state.ResultSet().IsDirty(2))
assert.True(t, srv.state.ResultSet().IsDirty(0))
assert.True(t, srv.state.ResultSet().IsDirty(2))
// Rescan the table and verify dirty items were not written
invokeCommandWithPrompt(t, readController.Rescan(), "y")
invokeCommandWithPrompt(t, srv.readController.Rescan(), "y")
assert.Equal(t, "This is some value", state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Nil(t, state.ResultSet().Items()[2]["alpha"])
assert.Equal(t, "This is some value", srv.state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Nil(t, srv.state.ResultSet().Items()[2]["alpha"])
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, state.ResultSet().IsDirty(2))
assert.False(t, srv.state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(2))
})
t.Run("do nothing if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
invokeCommand(t, srv.readController.Init())
// Modify the item and put it
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(2, models.StringItemType, "alpha"), "another new value")
invokeCommand(t, srv.writeController.ToggleMark(0))
invokeCommandExpectingError(t, srv.writeController.PutItems())
// Verify dirty items are unchanged
assert.Equal(t, "a new value", srv.state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Equal(t, "another new value", srv.state.ResultSet().Items()[2]["alpha"].(*types.AttributeValueMemberS).Value)
assert.True(t, srv.state.ResultSet().IsDirty(0))
assert.True(t, srv.state.ResultSet().IsDirty(2))
// Rescan the table and verify dirty items were not written
invokeCommandWithPrompt(t, srv.readController.Rescan(), "y")
assert.Equal(t, "This is some value", srv.state.ResultSet().Items()[0]["alpha"].(*types.AttributeValueMemberS).Value)
assert.Nil(t, srv.state.ResultSet().Items()[2]["alpha"])
assert.False(t, srv.state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(2))
})
}
func TestTableWriteController_TouchItem(t *testing.T) {
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
t.Run("should put the selected item if unmodified", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
// Read the table
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.TouchItem(0), "y")
invokeCommandWithPrompt(t, srv.writeController.TouchItem(0), "y")
// Rescan the table
invokeCommand(t, readController.Rescan())
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommand(t, srv.readController.Rescan())
after, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if modified", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
srv := newService(t, false)
// Read the table
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandExpectingError(t, writeController.TouchItem(0))
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandExpectingError(t, srv.writeController.TouchItem(0))
})
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
// Read the table
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandExpectingError(t, srv.writeController.TouchItem(0))
// Rescan the table
invokeCommand(t, srv.readController.Rescan())
after, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, srv.state.ResultSet().IsDirty(0))
})
}
func TestTableWriteController_NoisyTouchItem(t *testing.T) {
t.Run("should delete and put the selected item if unmodified", func(t *testing.T) {
srv := newService(t, false)
// Read the table
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandWithPrompt(t, srv.writeController.NoisyTouchItem(0), "y")
// Rescan the table
invokeCommand(t, srv.readController.Rescan())
after, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, srv.state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if modified", func(t *testing.T) {
srv := newService(t, false)
// Read the table
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandExpectingError(t, srv.writeController.NoisyTouchItem(0))
})
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
// Read the table
invokeCommand(t, srv.readController.Init())
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, srv.state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandExpectingError(t, srv.writeController.NoisyTouchItem(0))
})
}
func TestTableWriteController_DeleteMarked(t *testing.T) {
t.Run("should delete marked items", func(t *testing.T) {
srv := newService(t, false)
// Read the table
invokeCommand(t, srv.readController.Init())
assert.Len(t, srv.state.ResultSet().Items(), 3)
// Mark some items
invokeCommand(t, srv.writeController.ToggleMark(0))
invokeCommand(t, srv.writeController.ToggleMark(2))
// Delete it
invokeCommandWithPrompt(t, srv.writeController.DeleteMarked(), "y")
// Rescan and confirm marked items are deleted
invokeCommand(t, srv.readController.Init())
assert.Len(t, srv.state.ResultSet().Items(), 1)
after, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is another some value", after)
})
t.Run("should not delete marked items if in read-only mode", func(t *testing.T) {
srv := newService(t, true)
// Read the table
invokeCommand(t, srv.readController.Init())
assert.Len(t, srv.state.ResultSet().Items(), 3)
// Mark some items
invokeCommand(t, srv.writeController.ToggleMark(0))
invokeCommand(t, srv.writeController.ToggleMark(2))
// Delete it
invokeCommandExpectingError(t, srv.writeController.DeleteMarked())
// Rescan and confirm marked items are not deleted
invokeCommand(t, srv.readController.Init())
assert.Len(t, srv.state.ResultSet().Items(), 3)
after, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
})
}
type services struct {
state *controllers.State
readController *controllers.TableReadController
writeController *controllers.TableWriteController
settingsController *controllers.SettingsController
}
func newService(t *testing.T, isReadOnly bool) *services {
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
t.Run("should delete and put the selected item if unmodified", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
settingProvider := &mockedSetting{isReadOnly: isReadOnly}
provider := dynamo.NewProvider(client)
service := tables.NewService(provider, settingProvider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController, settingProvider)
settingsController := controllers.NewSettingsController(settingProvider)
// Read the table
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.NoisyTouchItem(0), "y")
// Rescan the table
invokeCommand(t, readController.Rescan())
after, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", after)
assert.False(t, state.ResultSet().IsDirty(0))
})
t.Run("should not put the selected item if modified", func(t *testing.T) {
client := testdynamo.SetupTestTable(t, testData)
provider := dynamo.NewProvider(client)
service := tables.NewService(provider)
state := controllers.NewState()
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
writeController := controllers.NewTableWriteController(state, service, readController)
// Read the table
invokeCommand(t, readController.Init())
before, _ := state.ResultSet().Items()[0].AttributeValueAsString("alpha")
assert.Equal(t, "This is some value", before)
assert.False(t, state.ResultSet().IsDirty(0))
// Modify the item and put it
invokeCommandWithPrompt(t, writeController.SetAttributeValue(0, models.StringItemType, "alpha"), "a new value")
invokeCommandExpectingError(t, writeController.NoisyTouchItem(0))
})
return &services{
state: state,
readController: readController,
writeController: writeController,
settingsController: settingsController,
}
}
type mockedSetting struct {
isReadOnly bool
}
func (ms *mockedSetting) SetReadOnly(ro bool) error {
ms.isReadOnly = ro
return nil
}
func (ms *mockedSetting) IsReadOnly() (bool, error) {
return ms.isReadOnly, nil
}

View file

@ -0,0 +1,5 @@
package models
import "github.com/pkg/errors"
var ErrReadOnly = errors.New("in read-only mode")

View file

@ -0,0 +1,31 @@
package settingstore
import (
"github.com/asdine/storm"
"github.com/lmika/audax/internal/common/workspaces"
)
const settingBucket = "Settings"
const (
keyTableReadOnly = "table_ro"
)
type SettingStore struct {
ws storm.Node
}
func New(ws *workspaces.Workspace) *SettingStore {
return &SettingStore{
ws: ws.DB(),
}
}
func (c *SettingStore) IsReadOnly() (b bool, err error) {
err = c.ws.Get(settingBucket, keyTableReadOnly, &b)
return b, err
}
func (c *SettingStore) SetReadOnly(ro bool) error {
return c.ws.Set(settingBucket, keyTableReadOnly, ro)
}

View file

@ -17,3 +17,7 @@ type TableProvider interface {
PutItem(ctx context.Context, name string, item models.Item) error
PutItems(ctx context.Context, name string, items []models.Item) error
}
type ROProvider interface {
IsReadOnly() (bool, error)
}

View file

@ -12,12 +12,14 @@ import (
)
type Service struct {
provider TableProvider
provider TableProvider
roProvider ROProvider
}
func NewService(provider TableProvider) *Service {
func NewService(provider TableProvider, roProvider ROProvider) *Service {
return &Service{
provider: provider,
provider: provider,
roProvider: roProvider,
}
}
@ -75,10 +77,18 @@ func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr
}
func (s *Service) Put(ctx context.Context, tableInfo *models.TableInfo, item models.Item) error {
if err := s.assertReadWrite(); err != nil {
return err
}
return s.provider.PutItem(ctx, tableInfo.Name, item)
}
func (s *Service) PutItemAt(ctx context.Context, resultSet *models.ResultSet, index int) error {
if err := s.assertReadWrite(); err != nil {
return err
}
item := resultSet.Items()[index]
if err := s.provider.PutItem(ctx, resultSet.TableInfo.Name, item); err != nil {
return err
@ -90,6 +100,10 @@ func (s *Service) PutItemAt(ctx context.Context, resultSet *models.ResultSet, in
}
func (s *Service) PutSelectedItems(ctx context.Context, resultSet *models.ResultSet, markedItems []models.ItemIndex) error {
if err := s.assertReadWrite(); err != nil {
return err
}
if len(markedItems) == 0 {
return nil
}
@ -108,6 +122,10 @@ func (s *Service) PutSelectedItems(ctx context.Context, resultSet *models.Result
}
func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items []models.Item) error {
if err := s.assertReadWrite(); err != nil {
return err
}
for _, item := range items {
if err := s.provider.DeleteItem(ctx, tableInfo.Name, item.KeyValue(tableInfo)); err != nil {
return errors.Wrapf(err, "cannot delete item")
@ -120,6 +138,16 @@ func (s *Service) ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo,
return s.doScan(ctx, tableInfo, expr)
}
func (s *Service) assertReadWrite() error {
b, err := s.roProvider.IsReadOnly()
if err != nil {
return err
} else if b {
return models.ErrReadOnly
}
return nil
}
// TODO: move into a new service
func (s *Service) Filter(resultSet *models.ResultSet, filter string) *models.ResultSet {
for i, item := range resultSet.Items() {

View file

@ -19,7 +19,7 @@ func TestService_Describe(t *testing.T) {
t.Run("return details of the table", func(t *testing.T) {
ctx := context.Background()
service := tables.NewService(provider)
service := tables.NewService(provider, mockedReadOnlyProvider{readOnly: false})
ti, err := service.Describe(ctx, tableName)
assert.NoError(t, err)
@ -40,7 +40,7 @@ func TestService_Scan(t *testing.T) {
t.Run("return all columns and fields in sorted order", func(t *testing.T) {
ctx := context.Background()
service := tables.NewService(provider)
service := tables.NewService(provider, mockedReadOnlyProvider{readOnly: false})
ti, err := service.Describe(ctx, tableName)
assert.NoError(t, err)
@ -77,3 +77,11 @@ var testData = []testdynamo.TestData{
},
},
}
type mockedReadOnlyProvider struct {
readOnly bool
}
func (m mockedReadOnlyProvider) IsReadOnly() (bool, error) {
return m.readOnly, nil
}

View file

@ -39,6 +39,7 @@ const (
type Model struct {
tableReadController *controllers.TableReadController
tableWriteController *controllers.TableWriteController
settingsController *controllers.SettingsController
commandController *commandctrl.CommandController
itemEdit *dynamoitemedit.Model
statusAndPrompt *statusandprompt.StatusAndPrompt
@ -56,6 +57,7 @@ type Model struct {
func NewModel(
rc *controllers.TableReadController,
wc *controllers.TableWriteController,
settingsController *controllers.SettingsController,
itemRendererService *itemrenderer.Service,
cc *commandctrl.CommandController,
keyBindingController *controllers.KeyBindingController,
@ -63,7 +65,7 @@ func NewModel(
) Model {
uiStyles := styles.DefaultStyles
dtv := dynamotableview.New(defaultKeyMap.TableView, uiStyles)
dtv := dynamotableview.New(defaultKeyMap.TableView, settingsController, uiStyles)
div := dynamoitemview.New(itemRendererService, uiStyles)
mainView := layout.NewVBox(layout.LastChildFixedAt(14), dtv, div)
@ -141,6 +143,15 @@ func NewModel(
}
return events.StatusMsg(s.String())
},
"set": func(ctx commandctrl.ExecContext, args []string) tea.Msg {
switch len(args) {
case 1:
return settingsController.SetSetting(args[0], "")
case 2:
return settingsController.SetSetting(args[0], args[1])
}
return events.Error(errors.New("expected: settingName [value]"))
},
"rebind": func(ctx commandctrl.ExecContext, args []string) tea.Msg {
if len(args) != 2 {
return events.Error(errors.New("expected: bindingName newKey"))

View file

@ -12,6 +12,7 @@ import (
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles"
table "github.com/lmika/go-bubble-table"
"strings"
)
var (
@ -21,29 +22,38 @@ var (
Background(lipgloss.Color("#4479ff"))
)
type Setting interface {
IsReadOnly() bool
}
type Model struct {
frameTitle frame.FrameTitle
table table.Model
w, h int
keyBinding *keybindings.TableKeyBinding
setting Setting
// model state
colOffset int
rows []table.Row
resultSet *models.ResultSet
isReadOnly bool
colOffset int
rows []table.Row
resultSet *models.ResultSet
}
func New(keyBinding *keybindings.TableKeyBinding, uiStyles styles.Styles) *Model {
func New(keyBinding *keybindings.TableKeyBinding, setting Setting, uiStyles styles.Styles) *Model {
tbl := table.New(table.SimpleColumns([]string{"pk", "sk"}), 100, 100)
rows := make([]table.Row, 0)
tbl.SetRows(rows)
frameTitle := frame.NewFrameTitle("No table", true, uiStyles.Frames)
isReadOnly := setting.IsReadOnly()
return &Model{
isReadOnly: isReadOnly,
frameTitle: frameTitle,
table: tbl,
keyBinding: keyBinding,
setting: setting,
}
}
@ -57,6 +67,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.resultSet = msg.ResultSet
m.updateTable()
return m, m.postSelectedItemChanged
case controllers.SettingsUpdated:
m.updateTableHeading()
return m, nil
case tea.KeyMsg:
switch {
// Table nav
@ -113,10 +126,19 @@ func (m *Model) Resize(w, h int) layout.ResizingModel {
return m
}
func (m *Model) updateTable() {
m.colOffset = 0
func (m *Model) updateTableHeading() {
tableName := new(strings.Builder)
tableName.WriteString("Table: " + m.resultSet.TableInfo.Name)
if m.setting.IsReadOnly() {
tableName.WriteString(" [RO]")
}
m.frameTitle.SetTitle("Table: " + m.resultSet.TableInfo.Name)
m.frameTitle.SetTitle(tableName.String())
}
func (m *Model) updateTable() {
m.updateTableHeading()
m.colOffset = 0
m.rebuildTable()
}

View file

@ -7,6 +7,7 @@ import (
"github.com/lmika/audax/internal/common/sliceutils"
"github.com/lmika/audax/internal/common/ui/events"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils"
"log"
)
@ -36,11 +37,16 @@ func (s *StatusAndPrompt) Init() tea.Cmd {
}
func (s *StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cc utils.CmdCollector
switch msg := msg.(type) {
case events.ErrorMsg:
s.statusMessage = "Error: " + msg.Error()
case events.StatusMsg:
s.statusMessage = string(msg)
case events.WrappedStatusMsg:
s.statusMessage = string(msg.Message)
cc.Add(func() tea.Msg { return msg.Next })
case events.ModeMessage:
s.modeLine = string(msg)
case events.MessageWithStatus:
@ -86,9 +92,9 @@ func (s *StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
newModel, cmd := s.model.Update(msg)
newModel := cc.Collect(s.model.Update(msg))
s.model = newModel.(layout.ResizingModel)
return s, cmd
return s, cc.Cmd()
}
func (s *StatusAndPrompt) InPrompt() bool {

View file

@ -49,7 +49,7 @@ func main() {
}
dynamoProvider := dynamo.NewProvider(dynamoClient)
tableService := tables.NewService(dynamoProvider)
tableService := tables.NewService(dynamoProvider, notROService{})
_, _ = tableService, tableInfo
@ -103,3 +103,9 @@ func createTable(ctx context.Context, dynamoClient *dynamodb.Client, tableName s
}
return nil
}
type notROService struct{}
func (n notROService) IsReadOnly() (bool, error) {
return false, nil
}