issue-28: added default limit as a setting (#29)
This commit is contained in:
parent
93ec519127
commit
efdc7f9e25
|
@ -34,6 +34,7 @@ func main() {
|
|||
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 flagDefaultLimit = flag.Int("default-limit", 0, "default limit for queries and scans")
|
||||
var flagWorkspace = flag.String("w", "", "workspace file")
|
||||
flag.Parse()
|
||||
|
||||
|
@ -82,6 +83,11 @@ func main() {
|
|||
cli.Fatalf("unable to set read-only mode: %v", err)
|
||||
}
|
||||
}
|
||||
if *flagDefaultLimit > 0 {
|
||||
if err := settingStore.SetDefaultLimit(*flagDefaultLimit); err != nil {
|
||||
cli.Fatalf("unable to set default limit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
tableService := tables.NewService(dynamoProvider, settingStore)
|
||||
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
|
||||
|
|
|
@ -16,4 +16,6 @@ type TableReadService interface {
|
|||
type SettingsProvider interface {
|
||||
IsReadOnly() (bool, error)
|
||||
SetReadOnly(ro bool) error
|
||||
DefaultLimit() (limit int)
|
||||
SetDefaultLimit(limit int) error
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/lmika/audax/internal/common/ui/events"
|
||||
"github.com/pkg/errors"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type SettingsController struct {
|
||||
|
@ -35,7 +37,21 @@ func (sc *SettingsController) SetSetting(name string, value string) tea.Msg {
|
|||
Message: "In read-write mode",
|
||||
Next: SettingsUpdated{},
|
||||
}
|
||||
case "default-limit":
|
||||
newLimit, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "bad value: %v", value)
|
||||
}
|
||||
|
||||
if err := sc.settings.SetDefaultLimit(newLimit); err != nil {
|
||||
return events.Error(err)
|
||||
}
|
||||
return events.WrappedStatusMsg{
|
||||
Message: events.StatusMsg(fmt.Sprintf("Default query limit now %v", newLimit)),
|
||||
Next: SettingsUpdated{},
|
||||
}
|
||||
}
|
||||
|
||||
return events.Error(errors.Errorf("unrecognised setting: %v", name))
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func TestSettingsController_SetSetting(t *testing.T) {
|
||||
t.Run("read-only setting", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{})
|
||||
|
||||
msg := invokeCommand(t, srv.settingsController.SetSetting("ro", ""))
|
||||
|
||||
|
@ -19,7 +19,7 @@ func TestSettingsController_SetSetting(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("read-write setting", func(t *testing.T) {
|
||||
srv := newService(t, true)
|
||||
srv := newService(t, serviceConfig{isReadOnly: true})
|
||||
|
||||
msg := invokeCommand(t, srv.settingsController.SetSetting("rw", ""))
|
||||
|
||||
|
@ -27,4 +27,13 @@ func TestSettingsController_SetSetting(t *testing.T) {
|
|||
assert.IsType(t, events.WrappedStatusMsg{}, msg)
|
||||
assert.IsType(t, controllers.SettingsUpdated{}, msg.(events.WrappedStatusMsg).Next)
|
||||
})
|
||||
|
||||
t.Run("set default limit", func(t *testing.T) {
|
||||
srv := newService(t, serviceConfig{})
|
||||
|
||||
assert.Equal(t, 1000, srv.settingProvider.DefaultLimit())
|
||||
invokeCommand(t, srv.settingsController.SetSetting("default-limit", "20"))
|
||||
|
||||
assert.Equal(t, 20, srv.settingProvider.DefaultLimit())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,11 +6,6 @@ import (
|
|||
"github.com/lmika/audax/internal/common/ui/events"
|
||||
"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/workspacestore"
|
||||
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
|
||||
"github.com/lmika/audax/internal/dynamo-browse/services/tables"
|
||||
workspaces_service "github.com/lmika/audax/internal/dynamo-browse/services/workspaces"
|
||||
"github.com/lmika/audax/test/testdynamo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
|
@ -19,45 +14,28 @@ import (
|
|||
)
|
||||
|
||||
func TestTableReadController_InitTable(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, &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)
|
||||
srv := newService(t, serviceConfig{})
|
||||
|
||||
event := readController.Init()
|
||||
event := srv.readController.Init()
|
||||
|
||||
assert.IsType(t, controllers.PromptForTableMsg{}, event)
|
||||
})
|
||||
|
||||
t.Run("should scan table if table name provided", func(t *testing.T) {
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "", false)
|
||||
srv := newService(t, serviceConfig{})
|
||||
|
||||
event := readController.Init()
|
||||
event := srv.readController.Init()
|
||||
|
||||
assert.IsType(t, controllers.PromptForTableMsg{}, event)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTableReadController_ListTables(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, &mockedSetting{})
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "", false)
|
||||
|
||||
t.Run("returns a list of tables", func(t *testing.T) {
|
||||
event := readController.ListTables().(controllers.PromptForTableMsg)
|
||||
srv := newService(t, serviceConfig{})
|
||||
|
||||
event := srv.readController.ListTables().(controllers.PromptForTableMsg)
|
||||
|
||||
assert.Equal(t, []string{"alpha-table", "bravo-table"}, event.Tables)
|
||||
|
||||
|
@ -71,59 +49,46 @@ func TestTableReadController_ListTables(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTableReadController_Rescan(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, &mockedSetting{})
|
||||
state := controllers.NewState()
|
||||
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "bravo-table", false)
|
||||
|
||||
t.Run("should perform a rescan", func(t *testing.T) {
|
||||
invokeCommand(t, readController.Init())
|
||||
invokeCommand(t, readController.Rescan())
|
||||
srv := newService(t, serviceConfig{tableName: "bravo-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
invokeCommand(t, srv.readController.Rescan())
|
||||
})
|
||||
|
||||
t.Run("should prompt to rescan if any dirty rows", func(t *testing.T) {
|
||||
invokeCommand(t, readController.Init())
|
||||
srv := newService(t, serviceConfig{tableName: "bravo-table"})
|
||||
|
||||
state.ResultSet().SetDirty(0, true)
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
invokeCommandWithPrompt(t, readController.Rescan(), "y")
|
||||
srv.state.ResultSet().SetDirty(0, true)
|
||||
|
||||
assert.False(t, state.ResultSet().IsDirty(0))
|
||||
invokeCommandWithPrompt(t, srv.readController.Rescan(), "y")
|
||||
|
||||
assert.False(t, srv.state.ResultSet().IsDirty(0))
|
||||
})
|
||||
|
||||
t.Run("should not rescan if any dirty rows", func(t *testing.T) {
|
||||
invokeCommand(t, readController.Init())
|
||||
srv := newService(t, serviceConfig{tableName: "bravo-table"})
|
||||
|
||||
state.ResultSet().SetDirty(0, true)
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
invokeCommandWithPrompt(t, readController.Rescan(), "n")
|
||||
srv.state.ResultSet().SetDirty(0, true)
|
||||
|
||||
assert.True(t, state.ResultSet().IsDirty(0))
|
||||
invokeCommandWithPrompt(t, srv.readController.Rescan(), "n")
|
||||
|
||||
assert.True(t, srv.state.ResultSet().IsDirty(0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTableReadController_ExportCSV(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, &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) {
|
||||
srv := newService(t, serviceConfig{tableName: "bravo-table"})
|
||||
|
||||
tempFile := tempFile(t)
|
||||
|
||||
invokeCommand(t, readController.Init())
|
||||
invokeCommand(t, readController.ExportCSV(tempFile))
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
invokeCommand(t, srv.readController.ExportCSV(tempFile))
|
||||
|
||||
bts, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
|
@ -137,33 +102,26 @@ func TestTableReadController_ExportCSV(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should return error if result set is not set", func(t *testing.T) {
|
||||
tempFile := tempFile(t)
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "non-existant-table", false)
|
||||
srv := newService(t, serviceConfig{tableName: "non-existant-table"})
|
||||
|
||||
invokeCommandExpectingError(t, readController.Init())
|
||||
invokeCommandExpectingError(t, readController.ExportCSV(tempFile))
|
||||
tempFile := tempFile(t)
|
||||
|
||||
invokeCommandExpectingError(t, srv.readController.Init())
|
||||
invokeCommandExpectingError(t, srv.readController.ExportCSV(tempFile))
|
||||
})
|
||||
|
||||
// Hidden items?
|
||||
}
|
||||
|
||||
func TestTableReadController_Query(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, &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) {
|
||||
srv := newService(t, serviceConfig{tableName: "bravo-table"})
|
||||
|
||||
tempFile := tempFile(t)
|
||||
|
||||
invokeCommand(t, readController.Init())
|
||||
invokeCommandWithPrompts(t, readController.PromptForQuery(), `pk ^= "abc"`)
|
||||
invokeCommand(t, readController.ExportCSV(tempFile))
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
invokeCommandWithPrompts(t, srv.readController.PromptForQuery(), `pk ^= "abc"`)
|
||||
invokeCommand(t, srv.readController.ExportCSV(tempFile))
|
||||
|
||||
bts, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
|
@ -175,11 +133,12 @@ func TestTableReadController_Query(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should return error if result set is not set", func(t *testing.T) {
|
||||
tempFile := tempFile(t)
|
||||
readController := controllers.NewTableReadController(controllers.NewState(), service, workspaceService, itemRendererService, "non-existant-table", false)
|
||||
srv := newService(t, serviceConfig{tableName: "non-existant-table"})
|
||||
|
||||
invokeCommandExpectingError(t, readController.Init())
|
||||
invokeCommandExpectingError(t, readController.ExportCSV(tempFile))
|
||||
tempFile := tempFile(t)
|
||||
|
||||
invokeCommandExpectingError(t, srv.readController.Init())
|
||||
invokeCommandExpectingError(t, srv.readController.ExportCSV(tempFile))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/lmika/audax/internal/dynamo-browse/controllers"
|
||||
"github.com/lmika/audax/internal/dynamo-browse/models"
|
||||
"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"
|
||||
"github.com/lmika/audax/internal/dynamo-browse/services/tables"
|
||||
|
@ -17,7 +18,7 @@ import (
|
|||
|
||||
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) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
assert.Len(t, srv.state.ResultSet().Items(), 3)
|
||||
|
@ -38,7 +39,7 @@ func TestTableWriteController_NewItem(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should do nothing when in read-only mode", func(t *testing.T) {
|
||||
srv := newService(t, true)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
assert.Len(t, srv.state.ResultSet().Items(), 3)
|
||||
|
@ -86,7 +87,7 @@ 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) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
invokeCommandWithPrompt(t, srv.writeController.SetAttributeValue(0, models.UnsetItemType, scenario.attrKey), scenario.attrValue)
|
||||
|
@ -99,7 +100,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should use type of selected item for marked fields if unspecified", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
invokeCommand(t, srv.writeController.ToggleMark(0))
|
||||
|
@ -146,7 +147,7 @@ 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) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("alpha")
|
||||
|
@ -165,7 +166,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run(fmt.Sprintf("should change value of nested field to type %v", scenario.attrType), func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
|
@ -193,7 +194,7 @@ func TestTableWriteController_SetAttributeValue(t *testing.T) {
|
|||
|
||||
func TestTableWriteController_DeleteAttribute(t *testing.T) {
|
||||
t.Run("should delete top level attribute", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
before, _ := srv.state.ResultSet().Items()[0].AttributeValueAsString("age")
|
||||
|
@ -207,7 +208,7 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should delete attribute of map", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
|
@ -228,7 +229,7 @@ func TestTableWriteController_DeleteAttribute(t *testing.T) {
|
|||
|
||||
func TestTableWriteController_PutItem(t *testing.T) {
|
||||
t.Run("should put the selected item if dirty", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -248,7 +249,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should not put the selected item if user does not confirm", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -272,7 +273,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should not put the selected item if not dirty", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -284,7 +285,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
|
||||
srv := newService(t, true)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -310,7 +311,7 @@ func TestTableWriteController_PutItem(t *testing.T) {
|
|||
|
||||
func TestTableWriteController_PutItems(t *testing.T) {
|
||||
t.Run("should put all dirty items if none are marked", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
|
@ -331,7 +332,7 @@ func TestTableWriteController_PutItems(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("only put marked items", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
|
@ -360,7 +361,7 @@ func TestTableWriteController_PutItems(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("do not put marked items which are not dirty", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
|
@ -389,7 +390,7 @@ func TestTableWriteController_PutItems(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("do nothing if in read-only mode", func(t *testing.T) {
|
||||
srv := newService(t, true)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
|
@ -420,7 +421,7 @@ func TestTableWriteController_PutItems(t *testing.T) {
|
|||
|
||||
func TestTableWriteController_TouchItem(t *testing.T) {
|
||||
t.Run("should put the selected item if unmodified", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -439,7 +440,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should not put the selected item if modified", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -453,7 +454,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
|
||||
srv := newService(t, true)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -474,7 +475,7 @@ func TestTableWriteController_TouchItem(t *testing.T) {
|
|||
|
||||
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)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -493,7 +494,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should not put the selected item if modified", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -507,7 +508,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should not put the selected item if in read-only mode", func(t *testing.T) {
|
||||
srv := newService(t, true)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -522,7 +523,7 @@ func TestTableWriteController_NoisyTouchItem(t *testing.T) {
|
|||
|
||||
func TestTableWriteController_DeleteMarked(t *testing.T) {
|
||||
t.Run("should delete marked items", func(t *testing.T) {
|
||||
srv := newService(t, false)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table"})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -543,7 +544,7 @@ func TestTableWriteController_DeleteMarked(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should not delete marked items if in read-only mode", func(t *testing.T) {
|
||||
srv := newService(t, true)
|
||||
srv := newService(t, serviceConfig{tableName: "alpha-table", isReadOnly: true})
|
||||
|
||||
// Read the table
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
@ -566,44 +567,46 @@ func TestTableWriteController_DeleteMarked(t *testing.T) {
|
|||
|
||||
type services struct {
|
||||
state *controllers.State
|
||||
settingProvider controllers.SettingsProvider
|
||||
readController *controllers.TableReadController
|
||||
writeController *controllers.TableWriteController
|
||||
settingsController *controllers.SettingsController
|
||||
}
|
||||
|
||||
func newService(t *testing.T, isReadOnly bool) *services {
|
||||
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(testWorkspace(t))
|
||||
type serviceConfig struct {
|
||||
tableName string
|
||||
isReadOnly bool
|
||||
}
|
||||
|
||||
func newService(t *testing.T, cfg serviceConfig) *services {
|
||||
ws := testWorkspace(t)
|
||||
|
||||
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
|
||||
settingStore := settingstore.New(ws)
|
||||
workspaceService := workspaces_service.NewService(resultSetSnapshotStore)
|
||||
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
|
||||
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
||||
settingProvider := &mockedSetting{isReadOnly: isReadOnly}
|
||||
provider := dynamo.NewProvider(client)
|
||||
service := tables.NewService(provider, settingProvider)
|
||||
service := tables.NewService(provider, settingStore)
|
||||
|
||||
state := controllers.NewState()
|
||||
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, "alpha-table", false)
|
||||
writeController := controllers.NewTableWriteController(state, service, readController, settingProvider)
|
||||
settingsController := controllers.NewSettingsController(settingProvider)
|
||||
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, cfg.tableName, false)
|
||||
writeController := controllers.NewTableWriteController(state, service, readController, settingStore)
|
||||
settingsController := controllers.NewSettingsController(settingStore)
|
||||
|
||||
if cfg.isReadOnly {
|
||||
if err := settingStore.SetReadOnly(cfg.isReadOnly); err != nil {
|
||||
t.Errorf("cannot set ro: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &services{
|
||||
state: state,
|
||||
settingProvider: settingStore,
|
||||
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
|
||||
}
|
||||
|
|
|
@ -3,12 +3,17 @@ package settingstore
|
|||
import (
|
||||
"github.com/asdine/storm"
|
||||
"github.com/lmika/audax/internal/common/workspaces"
|
||||
"github.com/pkg/errors"
|
||||
"log"
|
||||
)
|
||||
|
||||
const settingBucket = "Settings"
|
||||
|
||||
const (
|
||||
keyTableReadOnly = "table_ro"
|
||||
keyTableReadOnly = "ro"
|
||||
keyTableDefaultLimit = "default_limit"
|
||||
|
||||
defaultsDefaultLimit = 1000
|
||||
)
|
||||
|
||||
type SettingStore struct {
|
||||
|
@ -22,10 +27,30 @@ func New(ws *workspaces.Workspace) *SettingStore {
|
|||
}
|
||||
|
||||
func (c *SettingStore) IsReadOnly() (b bool, err error) {
|
||||
err = c.ws.Get(settingBucket, keyTableReadOnly, &b)
|
||||
if err := c.ws.Get(settingBucket, keyTableReadOnly, &b); err != nil {
|
||||
if errors.Is(err, storm.ErrNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (c *SettingStore) SetReadOnly(ro bool) error {
|
||||
return c.ws.Set(settingBucket, keyTableReadOnly, ro)
|
||||
}
|
||||
|
||||
func (c *SettingStore) DefaultLimit() (limit int) {
|
||||
err := c.ws.Get(settingBucket, keyTableDefaultLimit, &limit)
|
||||
if err != nil {
|
||||
if !errors.Is(err, storm.ErrNotFound) {
|
||||
log.Printf("warn: cannot get default limit from workspace, using default value: %v", err)
|
||||
}
|
||||
return defaultsDefaultLimit
|
||||
}
|
||||
return limit
|
||||
}
|
||||
|
||||
func (c *SettingStore) SetDefaultLimit(limit int) error {
|
||||
return errors.Wrapf(c.ws.Set(settingBucket, keyTableDefaultLimit, &limit), "cannot set default limit to %v", limit)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ type TableProvider interface {
|
|||
PutItems(ctx context.Context, name string, items []models.Item) error
|
||||
}
|
||||
|
||||
type ROProvider interface {
|
||||
type ConfigProvider interface {
|
||||
IsReadOnly() (bool, error)
|
||||
DefaultLimit() int
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ import (
|
|||
|
||||
type Service struct {
|
||||
provider TableProvider
|
||||
roProvider ROProvider
|
||||
configProvider ConfigProvider
|
||||
}
|
||||
|
||||
func NewService(provider TableProvider, roProvider ROProvider) *Service {
|
||||
func NewService(provider TableProvider, roProvider ConfigProvider) *Service {
|
||||
return &Service{
|
||||
provider: provider,
|
||||
roProvider: roProvider,
|
||||
configProvider: roProvider,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,10 +32,10 @@ func (s *Service) Describe(ctx context.Context, table string) (*models.TableInfo
|
|||
}
|
||||
|
||||
func (s *Service) Scan(ctx context.Context, tableInfo *models.TableInfo) (*models.ResultSet, error) {
|
||||
return s.doScan(ctx, tableInfo, nil)
|
||||
return s.doScan(ctx, tableInfo, nil, s.configProvider.DefaultLimit())
|
||||
}
|
||||
|
||||
func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable) (*models.ResultSet, error) {
|
||||
func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable, limit int) (*models.ResultSet, error) {
|
||||
var (
|
||||
filterExpr *expression.Expression
|
||||
runAsQuery bool
|
||||
|
@ -54,10 +54,10 @@ func (s *Service) doScan(ctx context.Context, tableInfo *models.TableInfo, expr
|
|||
var results []models.Item
|
||||
if runAsQuery {
|
||||
log.Printf("executing query")
|
||||
results, err = s.provider.QueryItems(ctx, tableInfo.Name, filterExpr, 1000)
|
||||
results, err = s.provider.QueryItems(ctx, tableInfo.Name, filterExpr, limit)
|
||||
} else {
|
||||
log.Printf("executing scan")
|
||||
results, err = s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, 1000)
|
||||
results, err = s.provider.ScanItems(ctx, tableInfo.Name, filterExpr, limit)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -135,11 +135,11 @@ func (s *Service) Delete(ctx context.Context, tableInfo *models.TableInfo, items
|
|||
}
|
||||
|
||||
func (s *Service) ScanOrQuery(ctx context.Context, tableInfo *models.TableInfo, expr models.Queryable) (*models.ResultSet, error) {
|
||||
return s.doScan(ctx, tableInfo, expr)
|
||||
return s.doScan(ctx, tableInfo, expr, s.configProvider.DefaultLimit())
|
||||
}
|
||||
|
||||
func (s *Service) assertReadWrite() error {
|
||||
b, err := s.roProvider.IsReadOnly()
|
||||
b, err := s.configProvider.IsReadOnly()
|
||||
if err != nil {
|
||||
return err
|
||||
} else if b {
|
||||
|
|
|
@ -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, mockedReadOnlyProvider{readOnly: false})
|
||||
service := tables.NewService(provider, mockedConfigProvider{readOnly: false})
|
||||
ti, err := service.Describe(ctx, tableName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -40,17 +40,30 @@ 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, mockedReadOnlyProvider{readOnly: false})
|
||||
service := tables.NewService(provider, mockedConfigProvider{readOnly: false})
|
||||
ti, err := service.Describe(ctx, tableName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rs, err := service.Scan(ctx, ti)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, rs.Items(), 3)
|
||||
|
||||
// Hash first, then range, then columns in alphabetic order
|
||||
assert.Equal(t, rs.TableInfo, ti)
|
||||
assert.Equal(t, rs.Columns(), []string{"pk", "sk", "alpha", "beta", "gamma"})
|
||||
})
|
||||
|
||||
t.Run("should honour default limits", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
service := tables.NewService(provider, mockedConfigProvider{readOnly: false, defaultLimit: 2})
|
||||
ti, err := service.Describe(ctx, tableName)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rs, err := service.Scan(ctx, ti)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, rs.Items(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
var testData = []testdynamo.TestData{
|
||||
|
@ -78,10 +91,18 @@ var testData = []testdynamo.TestData{
|
|||
},
|
||||
}
|
||||
|
||||
type mockedReadOnlyProvider struct {
|
||||
type mockedConfigProvider struct {
|
||||
readOnly bool
|
||||
defaultLimit int
|
||||
}
|
||||
|
||||
func (m mockedReadOnlyProvider) IsReadOnly() (bool, error) {
|
||||
func (m mockedConfigProvider) IsReadOnly() (bool, error) {
|
||||
return m.readOnly, nil
|
||||
}
|
||||
|
||||
func (m mockedConfigProvider) DefaultLimit() int {
|
||||
if m.defaultLimit == 0 {
|
||||
return 1000
|
||||
}
|
||||
return m.defaultLimit
|
||||
}
|
||||
|
|
|
@ -106,6 +106,10 @@ func createTable(ctx context.Context, dynamoClient *dynamodb.Client, tableName s
|
|||
|
||||
type notROService struct{}
|
||||
|
||||
func (n notROService) DefaultLimit() int {
|
||||
return 1000
|
||||
}
|
||||
|
||||
func (n notROService) IsReadOnly() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue