diff --git a/internal/dynamo-browse/controllers/iface.go b/internal/dynamo-browse/controllers/iface.go new file mode 100644 index 0000000..7c74234 --- /dev/null +++ b/internal/dynamo-browse/controllers/iface.go @@ -0,0 +1,13 @@ +package controllers + +import ( + "context" + "github.com/lmika/awstools/internal/dynamo-browse/models" +) + +type TableReadService interface { + ListTables(background context.Context) ([]string, error) + Describe(ctx context.Context, table string) (*models.TableInfo, error) + Scan(ctx context.Context, tableInfo *models.TableInfo) (*models.ResultSet, error) + Filter(resultSet *models.ResultSet, filter string) *models.ResultSet +} diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go index d185179..6f019be 100644 --- a/internal/dynamo-browse/controllers/tableread.go +++ b/internal/dynamo-browse/controllers/tableread.go @@ -5,13 +5,12 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/lmika/awstools/internal/common/ui/events" "github.com/lmika/awstools/internal/dynamo-browse/models" - "github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/pkg/errors" "sync" ) type TableReadController struct { - tableService *tables.Service + tableService TableReadService tableName string // state @@ -20,7 +19,7 @@ type TableReadController struct { filter string } -func NewTableReadController(tableService *tables.Service, tableName string) *TableReadController { +func NewTableReadController(tableService TableReadService, tableName string) *TableReadController { return &TableReadController{ tableService: tableService, tableName: tableName, diff --git a/internal/dynamo-browse/controllers/tableread_test.go b/internal/dynamo-browse/controllers/tableread_test.go new file mode 100644 index 0000000..e0ef160 --- /dev/null +++ b/internal/dynamo-browse/controllers/tableread_test.go @@ -0,0 +1,107 @@ +package controllers_test + +import ( + "github.com/lmika/awstools/internal/dynamo-browse/controllers" + "github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/awstools/internal/dynamo-browse/services/tables" + "github.com/lmika/awstools/test/testdynamo" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestTableReadController_InitTable(t *testing.T) { + client, cleanupFn := testdynamo.SetupTestTable(t, testData) + defer cleanupFn() + + provider := dynamo.NewProvider(client) + service := tables.NewService(provider) + + t.Run("should prompt for table if no table name provided", func(t *testing.T) { + readController := controllers.NewTableReadController(service, "") + + cmd := readController.Init() + event := cmd() + + assert.IsType(t, controllers.PromptForTableMsg{}, event) + }) + + t.Run("should scan table if table name provided", func(t *testing.T) { + readController := controllers.NewTableReadController(service, "") + + cmd := readController.Init() + event := cmd() + + assert.IsType(t, controllers.PromptForTableMsg{}, event) + }) +} + +func TestTableReadController_ListTables(t *testing.T) { + client, cleanupFn := testdynamo.SetupTestTable(t, testData) + defer cleanupFn() + + provider := dynamo.NewProvider(client) + service := tables.NewService(provider) + readController := controllers.NewTableReadController(service, "") + + t.Run("returns a list of tables", func(t *testing.T) { + cmd := readController.ListTables() + event := cmd().(controllers.PromptForTableMsg) + + assert.Equal(t, []string{"alpha-table", "bravo-table"}, event.Tables) + + selectedCmd := event.OnSelected("alpha-table") + selectedEvent := selectedCmd() + + resultSet := selectedEvent.(controllers.NewResultSet) + assert.Equal(t, "alpha-table", resultSet.ResultSet.TableInfo.Name) + assert.Equal(t, "pk", resultSet.ResultSet.TableInfo.Keys.PartitionKey) + assert.Equal(t, "sk", resultSet.ResultSet.TableInfo.Keys.SortKey) + }) +} + +var testData = []testdynamo.TestData{ + { + TableName: "alpha-table", + Data: []map[string]interface{}{ + { + "pk": "abc", + "sk": "111", + "alpha": "This is some value", + }, + { + "pk": "abc", + "sk": "222", + "alpha": "This is another some value", + "beta": 1231, + }, + { + "pk": "bbb", + "sk": "131", + "beta": 2468, + "gamma": "foobar", + }, + }, + }, + { + TableName: "bravo-table", + Data: []map[string]interface{}{ + { + "pk": "foo", + "sk": "bar", + "alpha": "This is some value", + }, + { + "pk": "abc", + "sk": "222", + "alpha": "This is another some value", + "beta": 1231, + }, + { + "pk": "bbb", + "sk": "131", + "beta": 2468, + "gamma": "foobar", + }, + }, + }, +} diff --git a/internal/dynamo-browse/controllers/tablewrite_test.go b/internal/dynamo-browse/controllers/tablewrite_test.go index ac0c49a..a8f5c1a 100644 --- a/internal/dynamo-browse/controllers/tablewrite_test.go +++ b/internal/dynamo-browse/controllers/tablewrite_test.go @@ -2,11 +2,6 @@ package controllers_test import ( "testing" - - "github.com/lmika/awstools/internal/dynamo-browse/controllers" - "github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo" - "github.com/lmika/awstools/internal/dynamo-browse/services/tables" - "github.com/lmika/awstools/test/testdynamo" ) func TestTableWriteController_ToggleReadWrite(t *testing.T) { @@ -159,6 +154,7 @@ func TestTableWriteController_Delete(t *testing.T) { */ } +/* type controller struct { tableName string tableService *tables.Service @@ -197,3 +193,4 @@ var testData = testdynamo.TestData{ "gamma": "foobar", }, } +*/ diff --git a/internal/dynamo-browse/providers/dynamo/provider_test.go b/internal/dynamo-browse/providers/dynamo/provider_test.go index a408bc2..1540792 100644 --- a/internal/dynamo-browse/providers/dynamo/provider_test.go +++ b/internal/dynamo-browse/providers/dynamo/provider_test.go @@ -11,9 +11,9 @@ import ( ) func TestProvider_ScanItems(t *testing.T) { - tableName := "provider-scanimages-test-table" + tableName := "test-table" - client, cleanupFn := testdynamo.SetupTestTable(t, tableName, testData) + client, cleanupFn := testdynamo.SetupTestTable(t, testData) defer cleanupFn() provider := dynamo.NewProvider(client) @@ -24,9 +24,9 @@ func TestProvider_ScanItems(t *testing.T) { assert.NoError(t, err) assert.Len(t, items, 3) - assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0])) - assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[1])) - assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[2])) + assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[0])) + assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[1])) + assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[2])) }) t.Run("should return error if table name does not exist", func(t *testing.T) { @@ -39,10 +39,10 @@ func TestProvider_ScanItems(t *testing.T) { } func TestProvider_DeleteItem(t *testing.T) { - tableName := "provider-deleteitem-test-table" + tableName := "test-table" t.Run("should delete item if exists in table", func(t *testing.T) { - client, cleanupFn := testdynamo.SetupTestTable(t, tableName, testData) + client, cleanupFn := testdynamo.SetupTestTable(t, testData) defer cleanupFn() provider := dynamo.NewProvider(client) @@ -57,14 +57,14 @@ func TestProvider_DeleteItem(t *testing.T) { assert.NoError(t, err) assert.Len(t, items, 2) - assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0])) - assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[2])) - assert.NotContains(t, items, testdynamo.TestRecordAsItem(t, testData[1])) + assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[0])) + assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[2])) + assert.NotContains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[1])) }) t.Run("should do nothing if key does not exist", func(t *testing.T) { - client, cleanupFn := testdynamo.SetupTestTable(t, tableName, testData) + client, cleanupFn := testdynamo.SetupTestTable(t, testData) defer cleanupFn() provider := dynamo.NewProvider(client) @@ -79,13 +79,13 @@ func TestProvider_DeleteItem(t *testing.T) { assert.NoError(t, err) assert.Len(t, items, 3) - assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0])) - assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[1])) - assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[2])) + assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[0])) + assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[1])) + assert.Contains(t, items, testdynamo.TestRecordAsItem(t, testData[0].Data[2])) }) t.Run("should return error if table name does not exist", func(t *testing.T) { - client, cleanupFn := testdynamo.SetupTestTable(t, tableName, testData) + client, cleanupFn := testdynamo.SetupTestTable(t, testData) defer cleanupFn() provider := dynamo.NewProvider(client) @@ -97,22 +97,27 @@ func TestProvider_DeleteItem(t *testing.T) { }) } -var testData = testdynamo.TestData{ +var testData = []testdynamo.TestData{ { - "pk": "abc", - "sk": "111", - "alpha": "This is some value", - }, - { - "pk": "abc", - "sk": "222", - "alpha": "This is another some value", - "beta": 1231, - }, - { - "pk": "bbb", - "sk": "131", - "beta": 2468, - "gamma": "foobar", + TableName: "test-table", + Data: []map[string]interface{}{ + { + "pk": "abc", + "sk": "111", + "alpha": "This is some value", + }, + { + "pk": "abc", + "sk": "222", + "alpha": "This is another some value", + "beta": 1231, + }, + { + "pk": "bbb", + "sk": "131", + "beta": 2468, + "gamma": "foobar", + }, + }, }, } diff --git a/internal/dynamo-browse/services/tables/service_test.go b/internal/dynamo-browse/services/tables/service_test.go index 9edc13c..3c559c7 100644 --- a/internal/dynamo-browse/services/tables/service_test.go +++ b/internal/dynamo-browse/services/tables/service_test.go @@ -11,9 +11,9 @@ import ( ) func TestService_Describe(t *testing.T) { - tableName := "service-describe-table" + tableName := "service-test-data" - client, cleanupFn := testdynamo.SetupTestTable(t, tableName, testData) + client, cleanupFn := testdynamo.SetupTestTable(t, testData) defer cleanupFn() provider := dynamo.NewProvider(client) @@ -33,9 +33,9 @@ func TestService_Describe(t *testing.T) { } func TestService_Scan(t *testing.T) { - tableName := "service-scan-test-table" + tableName := "service-test-data" - client, cleanupFn := testdynamo.SetupTestTable(t, tableName, testData) + client, cleanupFn := testdynamo.SetupTestTable(t, testData) defer cleanupFn() provider := dynamo.NewProvider(client) @@ -58,22 +58,27 @@ func TestService_Scan(t *testing.T) { }) } -var testData = testdynamo.TestData{ +var testData = []testdynamo.TestData{ { - "pk": "abc", - "sk": "222", - "alpha": "This is another some value", - "beta": 1231, - }, - { - "pk": "abc", - "sk": "111", - "alpha": "This is some value", - }, - { - "pk": "bbb", - "sk": "131", - "beta": 2468, - "gamma": "foobar", + TableName: "service-test-data", + Data: []map[string]interface{}{ + { + "pk": "abc", + "sk": "222", + "alpha": "This is another some value", + "beta": 1231, + }, + { + "pk": "abc", + "sk": "111", + "alpha": "This is some value", + }, + { + "pk": "bbb", + "sk": "131", + "beta": 2468, + "gamma": "foobar", + }, + }, }, } diff --git a/internal/slog-view/ui/fullviewlinedetails/model.go b/internal/slog-view/ui/fullviewlinedetails/model.go index 6b0aca5..778841e 100644 --- a/internal/slog-view/ui/fullviewlinedetails/model.go +++ b/internal/slog-view/ui/fullviewlinedetails/model.go @@ -8,7 +8,7 @@ import ( ) type Model struct { - submodel tea.Model + submodel tea.Model lineDetails *linedetails.Model visible bool @@ -16,7 +16,7 @@ type Model struct { func NewModel(submodel tea.Model) *Model { return &Model{ - submodel: submodel, + submodel: submodel, lineDetails: linedetails.New(), } } @@ -49,6 +49,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *Model) ViewItem(item *models.LogLine) { m.visible = true m.lineDetails.SetSelectedItem(item) + m.lineDetails.SetFocused(true) } func (m *Model) View() string { diff --git a/internal/ssm-browse/ui/ssmlist/tblmodel.go b/internal/ssm-browse/ui/ssmlist/tblmodel.go index d7c0d7e..6a28598 100644 --- a/internal/ssm-browse/ui/ssmlist/tblmodel.go +++ b/internal/ssm-browse/ui/ssmlist/tblmodel.go @@ -14,7 +14,7 @@ type itemTableRow struct { func (mtr itemTableRow) Render(w io.Writer, model table.Model, index int) { firstLine := strings.SplitN(mtr.item.Value, "\n", 2)[0] - line := fmt.Sprintf("%s\t%s\t%s", mtr.item.Name, "String", firstLine) + line := fmt.Sprintf("%s\t%s\t%s", mtr.item.Name, mtr.item.Type, firstLine) if index == model.Cursor() { fmt.Fprintln(w, model.Styles.SelectedRow.Render(line)) diff --git a/test/testdynamo/client.go b/test/testdynamo/client.go index bb7be11..af3842e 100644 --- a/test/testdynamo/client.go +++ b/test/testdynamo/client.go @@ -13,9 +13,12 @@ import ( "github.com/stretchr/testify/assert" ) -type TestData []map[string]interface{} +type TestData struct { + TableName string + Data []map[string]interface{} +} -func SetupTestTable(t *testing.T, tableName string, testData TestData) (*dynamodb.Client, func()) { +func SetupTestTable(t *testing.T, testData []TestData) (*dynamodb.Client, func()) { t.Helper() ctx := context.Background() @@ -27,37 +30,41 @@ func SetupTestTable(t *testing.T, tableName string, testData TestData) (*dynamod dynamoClient := dynamodb.NewFromConfig(cfg, dynamodb.WithEndpointResolver(dynamodb.EndpointResolverFromURL("http://localhost:8000"))) - _, err = dynamoClient.CreateTable(ctx, &dynamodb.CreateTableInput{ - TableName: aws.String(tableName), - KeySchema: []types.KeySchemaElement{ - {AttributeName: aws.String("pk"), KeyType: types.KeyTypeHash}, - {AttributeName: aws.String("sk"), KeyType: types.KeyTypeRange}, - }, - AttributeDefinitions: []types.AttributeDefinition{ - {AttributeName: aws.String("pk"), AttributeType: types.ScalarAttributeTypeS}, - {AttributeName: aws.String("sk"), AttributeType: types.ScalarAttributeTypeS}, - }, - ProvisionedThroughput: &types.ProvisionedThroughput{ - ReadCapacityUnits: aws.Int64(100), - WriteCapacityUnits: aws.Int64(100), - }, - }) - assert.NoError(t, err) - - for _, item := range testData { - m, err := attributevalue.MarshalMap(item) - assert.NoError(t, err) - - _, err = dynamoClient.PutItem(ctx, &dynamodb.PutItemInput{ - TableName: aws.String(tableName), - Item: m, + for _, table := range testData { + _, err = dynamoClient.CreateTable(ctx, &dynamodb.CreateTableInput{ + TableName: aws.String(table.TableName), + KeySchema: []types.KeySchemaElement{ + {AttributeName: aws.String("pk"), KeyType: types.KeyTypeHash}, + {AttributeName: aws.String("sk"), KeyType: types.KeyTypeRange}, + }, + AttributeDefinitions: []types.AttributeDefinition{ + {AttributeName: aws.String("pk"), AttributeType: types.ScalarAttributeTypeS}, + {AttributeName: aws.String("sk"), AttributeType: types.ScalarAttributeTypeS}, + }, + ProvisionedThroughput: &types.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(100), + WriteCapacityUnits: aws.Int64(100), + }, }) assert.NoError(t, err) + + for _, item := range table.Data { + m, err := attributevalue.MarshalMap(item) + assert.NoError(t, err) + + _, err = dynamoClient.PutItem(ctx, &dynamodb.PutItemInput{ + TableName: aws.String(table.TableName), + Item: m, + }) + assert.NoError(t, err) + } } return dynamoClient, func() { - dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{ - TableName: aws.String(tableName), - }) + for _, table := range testData { + dynamoClient.DeleteTable(ctx, &dynamodb.DeleteTableInput{ + TableName: aws.String(table.TableName), + }) + } } }