-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
api: adds CRUD integration types and test cases (#46)
* adds api for integration types * adds api for integration types * adds test cases * adds test cases * fix lint * restructure * fix case * fix the naming pattern
- Loading branch information
Showing
10 changed files
with
423 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package integrationtype | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"github.com/shinobistack/gokakashi/ent" | ||
"github.com/swaggest/usecase/status" | ||
) | ||
|
||
type GetIntegrationTypeRequests struct { | ||
ID string `path:"id"` | ||
} | ||
|
||
type GetIntegrationTypeResponse struct { | ||
ID string `json:"id"` | ||
DisplayName string `json:"display_name"` | ||
} | ||
|
||
func GetIntegrationType(client *ent.Client) func(ctx context.Context, req GetIntegrationTypeRequests, res *GetIntegrationTypeResponse) error { | ||
return func(ctx context.Context, req GetIntegrationTypeRequests, res *GetIntegrationTypeResponse) error { | ||
it, err := client.IntegrationType.Get(ctx, req.ID) | ||
if err != nil { | ||
if ent.IsNotFound(err) { | ||
return status.Wrap(errors.New("integration type not found"), status.NotFound) | ||
} | ||
return status.Wrap(err, status.Internal) | ||
} | ||
|
||
res.ID = it.ID | ||
res.DisplayName = it.DisplayName | ||
return nil | ||
|
||
} | ||
|
||
} | ||
|
||
func ListIntegrationType(client *ent.Client) func(ctx context.Context, req struct{}, res *[]GetIntegrationTypeResponse) error { | ||
return func(ctx context.Context, req struct{}, res *[]GetIntegrationTypeResponse) error { | ||
its, err := client.IntegrationType.Query().All(ctx) | ||
if err != nil { | ||
return status.Wrap(errors.New("failed to fetch integration types"), status.Internal) | ||
} | ||
|
||
*res = make([]GetIntegrationTypeResponse, len(its)) | ||
for i, it := range its { | ||
(*res)[i] = GetIntegrationTypeResponse{ | ||
ID: it.ID, | ||
DisplayName: it.DisplayName, | ||
} | ||
} | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package integrationtype_test | ||
|
||
import ( | ||
"context" | ||
_ "github.com/mattn/go-sqlite3" | ||
"github.com/shinobistack/gokakashi/ent/enttest" | ||
"github.com/shinobistack/gokakashi/internal/restapi/v1/integrationtype" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestGetIntegrationType_ValidID(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
// Seed data | ||
_, _ = client.IntegrationType.Create(). | ||
SetID("linear"). | ||
SetDisplayName("Linear Integration"). | ||
Save(context.Background()) | ||
|
||
// Test case: Valid ID | ||
req := integrationtype.GetIntegrationTypeRequests{ID: "linear"} | ||
res := &integrationtype.GetIntegrationTypeResponse{} | ||
handler := integrationtype.GetIntegrationType(client) | ||
err := handler(context.Background(), req, res) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, "linear", res.ID) | ||
assert.Equal(t, "Linear Integration", res.DisplayName) | ||
} | ||
|
||
func TestGetIntegrationType_NonExistentID(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
req := integrationtype.GetIntegrationTypeRequests{ID: "nonexistent"} | ||
res := &integrationtype.GetIntegrationTypeResponse{} | ||
handler := integrationtype.GetIntegrationType(client) | ||
err := handler(context.Background(), req, res) | ||
|
||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "integration type not found") | ||
} | ||
|
||
func TestGetIntegrationType_InvalidIDFormat(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
req := integrationtype.GetIntegrationTypeRequests{ID: "Invalid ID!"} | ||
res := &integrationtype.GetIntegrationTypeResponse{} | ||
handler := integrationtype.GetIntegrationType(client) | ||
err := handler(context.Background(), req, res) | ||
assert.Error(t, err) | ||
|
||
req = integrationtype.GetIntegrationTypeRequests{ID: "Inv*ID"} | ||
handler = integrationtype.GetIntegrationType(client) | ||
err = handler(context.Background(), req, res) | ||
assert.Error(t, err) | ||
} | ||
|
||
func TestListIntegrationTypes_ValidRequest(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
// Seed data | ||
_, _ = client.IntegrationType.Create(). | ||
SetID("linear"). | ||
SetDisplayName("Linear Integration"). | ||
Save(context.Background()) | ||
_, _ = client.IntegrationType.Create(). | ||
SetID("jira"). | ||
SetDisplayName("Jira Integration"). | ||
Save(context.Background()) | ||
|
||
req := struct{}{} | ||
var res []integrationtype.GetIntegrationTypeResponse | ||
handler := integrationtype.ListIntegrationType(client) | ||
err := handler(context.Background(), req, &res) | ||
|
||
assert.NoError(t, err) | ||
assert.Len(t, res, 2) | ||
} | ||
|
||
func TestListIntegrations_EmptyDB(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
// Prepare response | ||
var res []integrationtype.GetIntegrationTypeResponse | ||
|
||
// Execute ListIntegrations handler | ||
handler := integrationtype.ListIntegrationType(client) | ||
err := handler(context.Background(), struct{}{}, &res) | ||
|
||
// Validate response | ||
assert.NoError(t, err) | ||
assert.Len(t, res, 0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package integrationtype | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"github.com/shinobistack/gokakashi/ent" | ||
"github.com/swaggest/usecase/status" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
type CreateIntegrationTypeRequest struct { | ||
ID string `json:"id"` | ||
DisplayName string `json:"display_name"` | ||
} | ||
|
||
func CreateIntegrationType(client *ent.Client) func(ctx context.Context, req CreateIntegrationTypeRequest, res *GetIntegrationTypeResponse) error { | ||
return func(ctx context.Context, req CreateIntegrationTypeRequest, res *GetIntegrationTypeResponse) error { | ||
// Validate fields | ||
if req.ID == "" || req.DisplayName == "" { | ||
return status.Wrap(errors.New("missing required fields: id and/or display_name"), status.InvalidArgument) | ||
} | ||
|
||
if !isValidID(req.ID) { | ||
return status.Wrap(errors.New("invalid id format; must be lowercase, alphanumeric, or dashes"), status.InvalidArgument) // 400 Bad Request | ||
} | ||
|
||
// Create integration type | ||
it, err := client.IntegrationType.Create(). | ||
SetID(req.ID). | ||
SetDisplayName(req.DisplayName). | ||
Save(ctx) | ||
if err != nil { | ||
if ent.IsConstraintError(err) { | ||
return status.Wrap(errors.New("integration type already exists"), status.AlreadyExists) | ||
} | ||
return status.Wrap(fmt.Errorf("failed to create integration type: %v", err), status.Internal) | ||
} | ||
|
||
res.ID = it.ID | ||
res.DisplayName = it.DisplayName | ||
return nil | ||
} | ||
} | ||
|
||
// isValidID validates the ID format: | ||
// - All lowercase letters. | ||
// - Multiple words separated by dashes (`-`). | ||
// - No spaces at the beginning or end. | ||
// - No special characters other than hyphen. | ||
func isValidID(id string) bool { | ||
id = strings.TrimSpace(id) | ||
regex := regexp.MustCompile(`^[a-z]+(-[a-z]+)*$`) | ||
return regex.MatchString(id) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package integrationtype_test | ||
|
||
import ( | ||
"context" | ||
"github.com/shinobistack/gokakashi/internal/restapi/v1/integrationtype" | ||
"testing" | ||
|
||
"github.com/shinobistack/gokakashi/ent/enttest" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestCreateIntegrationType_ValidInput(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
req := integrationtype.CreateIntegrationTypeRequest{ | ||
ID: "linear", | ||
DisplayName: "Linear Integration", | ||
} | ||
res := &integrationtype.GetIntegrationTypeResponse{} | ||
handler := integrationtype.CreateIntegrationType(client) | ||
err := handler(context.Background(), req, res) | ||
|
||
assert.NoError(t, err) | ||
assert.Equal(t, "linear", res.ID) | ||
assert.Equal(t, "Linear Integration", res.DisplayName) | ||
} | ||
|
||
func TestCreateIntegrationType_MissingFields(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
req := integrationtype.CreateIntegrationTypeRequest{} | ||
res := &integrationtype.GetIntegrationTypeResponse{} | ||
handler := integrationtype.CreateIntegrationType(client) | ||
err := handler(context.Background(), req, res) | ||
|
||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "missing required fields") | ||
} | ||
|
||
func TestCreateIntegrationType_InvalidIDFormat(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
req := integrationtype.CreateIntegrationTypeRequest{ | ||
ID: "Invalid ID!", | ||
DisplayName: "Valid Format ", | ||
} | ||
res := &integrationtype.GetIntegrationTypeResponse{} | ||
handler := integrationtype.CreateIntegrationType(client) | ||
err := handler(context.Background(), req, res) | ||
assert.Error(t, err) | ||
|
||
req = integrationtype.CreateIntegrationTypeRequest{ | ||
ID: "invalid*#", | ||
DisplayName: "Valid Format ", | ||
} | ||
handler = integrationtype.CreateIntegrationType(client) | ||
err = handler(context.Background(), req, res) | ||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "invalid id format") | ||
} | ||
|
||
func TestCreateIntegrationType_DuplicateID(t *testing.T) { | ||
client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") | ||
defer client.Close() | ||
|
||
_, _ = client.IntegrationType.Create(). | ||
SetID("linear"). | ||
SetDisplayName("Linear Integration"). | ||
Save(context.Background()) | ||
|
||
req := integrationtype.CreateIntegrationTypeRequest{ | ||
ID: "linear", | ||
DisplayName: "Linear Integration", | ||
} | ||
res := &integrationtype.GetIntegrationTypeResponse{} | ||
handler := integrationtype.CreateIntegrationType(client) | ||
err := handler(context.Background(), req, res) | ||
|
||
assert.Error(t, err) | ||
assert.Contains(t, err.Error(), "integration type already exists") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package integrationtype | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"github.com/shinobistack/gokakashi/ent" | ||
"github.com/swaggest/usecase/status" | ||
) | ||
|
||
type UpdateIntegrationTypeRequest struct { | ||
ID string `path:"id"` | ||
DisplayName *string `json:"display_name"` | ||
} | ||
|
||
func UpdateIntegrationType(client *ent.Client) func(ctx context.Context, req UpdateIntegrationTypeRequest, res *GetIntegrationTypeResponse) error { | ||
return func(ctx context.Context, req UpdateIntegrationTypeRequest, res *GetIntegrationTypeResponse) error { | ||
if !isValidID(req.ID) { | ||
return status.Wrap(errors.New("invalid id format"), status.InvalidArgument) // 400 Bad Request | ||
} | ||
|
||
update := client.IntegrationType.UpdateOneID(req.ID) | ||
if req.DisplayName != nil { | ||
update = update.SetDisplayName(*req.DisplayName) | ||
} | ||
|
||
it, err := update.Save(ctx) | ||
if err != nil { | ||
if ent.IsNotFound(err) { | ||
return status.Wrap(errors.New("integration type not found"), status.NotFound) | ||
} | ||
return status.Wrap(fmt.Errorf("failed to update integration type: %v", err), status.Internal) | ||
} | ||
|
||
res.ID = it.ID | ||
res.DisplayName = it.DisplayName | ||
return nil | ||
} | ||
} |
Oops, something went wrong.