diff --git a/.mockery.yaml b/.mockery.yaml index a422248..cfd2f72 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -1,3 +1,2 @@ with-expecter: true keeptree: True - diff --git a/Taskfile.yml b/Taskfile.yml index 1801608..a4b7fa1 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,4 +1,4 @@ -version: '3' +version: "3" env: CGO_ENABLED: 0 @@ -17,9 +17,11 @@ vars: tasks: install_tools: cmds: - - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 - - go install github.com/a-h/templ/cmd/templ@v0.2.513 + - "[[ ! -f $GOBIN/golangci-lint ]] && go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 || echo golang-ci lint installed" + - "[[ ! -f $GOBIN/templ ]] && go install github.com/a-h/templ/cmd/templ@v0.2.513 || echo templ installed" + - "[[ ! -f $GOBIN/mockery ]] && go install github.com/vektra/mockery/v2@v2.42.1 || echo mockery installed" generate: + run: once cmds: - "$GOBIN/templ generate" sources: @@ -28,16 +30,27 @@ tasks: generates: - "internal/kurious/ports/http/templ/*.go" - "internal/kurious/ports/http/bootstrap/*.go" + deps: + - install_tools + mocks: + run: once + cmd: "go generate ./internal/..." + deps: + - install_tools check: + run: once cmds: - "$GOBIN/golangci-lint run ./..." deps: - generate + - mocks test: + run: once cmds: - go test ./internal/... deps: - generate + - mocks build_web: cmds: - go build -o $GOBIN/kuriousweb -v -ldflags '{{.LDFLAGS}}' cmd/kuriweb/*.go @@ -48,7 +61,7 @@ tasks: deps: [check, test] build_dev_cli: cmds: - - go build -o $GOBIN/sravnicli -v -ldflags '{{.LDFLAGS}}' cmd/dev/sravnicli/*.go + - go build -o $GOBIN/sravnicli -v -ldflags '{{.LDFLAGS}}' cmd/dev/sravnicli/*.go deps: [check, test] build: cmds: diff --git a/go.mod b/go.mod index 6693037..5c57410 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.loyso.art/frx/kurious -go 1.21 +go 1.22 require ( github.com/a-h/templ v0.2.513 @@ -30,6 +30,7 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162 // indirect github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd // indirect github.com/ydb-platform/ydb-go-yc-metadata v0.6.1 // indirect diff --git a/go.sum b/go.sum index dcd8cff..a5de74e 100644 --- a/go.sum +++ b/go.sum @@ -785,6 +785,8 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -798,6 +800,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/teris-io/cli v1.0.1 h1:J6jnVHC552uqx7zT+Ux0++tIvLmJQULqxVhCid2u/Gk= github.com/teris-io/cli v1.0.1/go.mod h1:V9nVD5aZ873RU/tQXLSXO8FieVPQhQvuNohsdsKXsGw= +github.com/vektra/mockery/v2 v2.42.1 h1:z7l3O4jCzRZat3rm9jpHc8lzpR8bs1VBii7bYtl3KQs= +github.com/vektra/mockery/v2 v2.42.1/go.mod h1:XNTE9RIu3deGAGQRVjP1VZxGpQNm0YedZx4oDs3prr8= github.com/yandex-cloud/go-genproto v0.0.0-20211115083454-9ca41db5ed9e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162 h1:xCzizLC090MiLWEV3aziL5YIKrSVTRXX2DXlRGeQ6sA= github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= diff --git a/htmlexamples/core.html b/htmlexamples/core.html index fd1f3bb..99784c7 100644 --- a/htmlexamples/core.html +++ b/htmlexamples/core.html @@ -1,183 +1,208 @@ - + - - Test page - - - - - - + + Test page + + + + + + + - - -
- - -
-
-
- -
-
- Lorem, ipsum dolor sit amet consectetur adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim - omnis vero, reiciendis obcaecati perferendis excepturi nostrum - nobis itaque modi dignissimos ... -

- -
- - -
-
- -
-
- -
-
- Lorem, ipsum dolor sit amet consectetur adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim - omnis vero, reiciendis obcaecati perferendis excepturi nostrum - nobis itaque modi dignissimos ... -

- Go somewhere -
- -
-
- -
-
- -
-
- Lorem, ipsum dolor sit amet consectetur adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim - omnis vero, reiciendis obcaecati perferendis excepturi nostrum - nobis itaque modi dignissimos ... -

- Go somewhere -
- -
-
- -
-
- -
-
- Lorem, ipsum dolor sit amet consectetur adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim - omnis vero, reiciendis obcaecati perferendis excepturi nostrum - nobis itaque modi dignissimos ... -

- Go somewhere -
- -
-
- -
-
- -
-
- Lorem, ipsum dolor sit amet consectetur adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim - omnis vero, reiciendis obcaecati perferendis excepturi nostrum - nobis itaque modi dignissimos ... -

- Go somewhere -
- -
-
-
-
- + diff --git a/htmlexamples/courses.html b/htmlexamples/courses.html index 5e52083..4508223 100644 --- a/htmlexamples/courses.html +++ b/htmlexamples/courses.html @@ -1,144 +1,206 @@ - + + + Test page + + + + + + - - Test page - - - - - - - - -
- -
- -
- -
- -
- -
- -
-
- Filter categories - - - - - -
- -
- -
- -
-

Languages

-

A languages category provides all courses to help learn language

- -
- -
- -
- -
- -
- -
-
Promocodes
-
-
- -
-

Japanese

-

Looking for a course to learn japanese language?

- -
- -
-
- ... -
-
Card title with a long naming
-
- Open > - 500$ + +
+
-
-
+ + +
+
+ +
+ +
+
+
+ Filter categories + + + + + +
+
+
+ +
+

Languages

+

+ A languages category provides all courses to help learn + language +

+ +
+
+ +
+ +
+ +
+ +
+
Promocodes
+
+
+ +
+

Japanese

+

Looking for a course to learn japanese language?

+ +
+
+
+ ... +
+
+ Card title with a long naming +
+
+ Open > + 500$ +
+
+
+
+
+
+ +
+
+ +
+
+

(c) All right reserved

+
+
- -
- -
- -
- - -
- - + + diff --git a/htmlexamples/index.html b/htmlexamples/index.html index 53a246e..f3b7fe1 100644 --- a/htmlexamples/index.html +++ b/htmlexamples/index.html @@ -1,93 +1,126 @@ - + + + Test page + + + + + + - - Test page - - - - - - + +
+ +
- -
- -
- -
-
-

Some header about courses

-
- -
-
-
    -

    Category 1

    -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
-
-
-
    -

    Category 2

    -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
-
-
-
    -

    Category 3

    -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
-
-
- -
- + + diff --git a/internal/common/client/sravni/client.go b/internal/common/client/sravni/client.go index 3dbb508..98ac4c2 100644 --- a/internal/common/client/sravni/client.go +++ b/internal/common/client/sravni/client.go @@ -31,7 +31,7 @@ type Client interface { ListEducationalProducts( ctx context.Context, params ListEducationProductsParams, - ) (result listEducationProductsResponse, err error) + ) (result ListEducationProductsResponse, err error) ListEducationalProductsFilterCount( ctx context.Context, params ListEducationProductsParams, @@ -177,7 +177,7 @@ type listEducationProductsRequest struct { SortDirection string `json:"sortDirection"` } -type listEducationProductsResponse struct { +type ListEducationProductsResponse struct { Items []Course `json:"items"` Organizations map[string]Organization `json:"organizations"` @@ -188,7 +188,7 @@ type listEducationProductsResponse struct { func (c *client) ListEducationalProducts( ctx context.Context, params ListEducationProductsParams, -) (result listEducationProductsResponse, err error) { +) (result ListEducationProductsResponse, err error) { const urlPath = "/v1/education/products" const defaultLimit = 1 const defaultSortProp = "advertising.position" diff --git a/internal/common/client/sravni/mocks/Client.go b/internal/common/client/sravni/mocks/Client.go new file mode 100644 index 0000000..8417ce2 --- /dev/null +++ b/internal/common/client/sravni/mocks/Client.go @@ -0,0 +1,208 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + sravni "git.loyso.art/frx/kurious/internal/common/client/sravni" + mock "github.com/stretchr/testify/mock" +) + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +type Client_Expecter struct { + mock *mock.Mock +} + +func (_m *Client) EXPECT() *Client_Expecter { + return &Client_Expecter{mock: &_m.Mock} +} + +// GetMainPageState provides a mock function with given fields: +func (_m *Client) GetMainPageState() (*sravni.PageState, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetMainPageState") + } + + var r0 *sravni.PageState + var r1 error + if rf, ok := ret.Get(0).(func() (*sravni.PageState, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *sravni.PageState); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sravni.PageState) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_GetMainPageState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMainPageState' +type Client_GetMainPageState_Call struct { + *mock.Call +} + +// GetMainPageState is a helper method to define mock.On call +func (_e *Client_Expecter) GetMainPageState() *Client_GetMainPageState_Call { + return &Client_GetMainPageState_Call{Call: _e.mock.On("GetMainPageState")} +} + +func (_c *Client_GetMainPageState_Call) Run(run func()) *Client_GetMainPageState_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Client_GetMainPageState_Call) Return(_a0 *sravni.PageState, _a1 error) *Client_GetMainPageState_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_GetMainPageState_Call) RunAndReturn(run func() (*sravni.PageState, error)) *Client_GetMainPageState_Call { + _c.Call.Return(run) + return _c +} + +// ListEducationalProducts provides a mock function with given fields: ctx, params +func (_m *Client) ListEducationalProducts(ctx context.Context, params sravni.ListEducationProductsParams) (sravni.ListEducationProductsResponse, error) { + ret := _m.Called(ctx, params) + + if len(ret) == 0 { + panic("no return value specified for ListEducationalProducts") + } + + var r0 sravni.ListEducationProductsResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, sravni.ListEducationProductsParams) (sravni.ListEducationProductsResponse, error)); ok { + return rf(ctx, params) + } + if rf, ok := ret.Get(0).(func(context.Context, sravni.ListEducationProductsParams) sravni.ListEducationProductsResponse); ok { + r0 = rf(ctx, params) + } else { + r0 = ret.Get(0).(sravni.ListEducationProductsResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context, sravni.ListEducationProductsParams) error); ok { + r1 = rf(ctx, params) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_ListEducationalProducts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEducationalProducts' +type Client_ListEducationalProducts_Call struct { + *mock.Call +} + +// ListEducationalProducts is a helper method to define mock.On call +// - ctx context.Context +// - params sravni.ListEducationProductsParams +func (_e *Client_Expecter) ListEducationalProducts(ctx interface{}, params interface{}) *Client_ListEducationalProducts_Call { + return &Client_ListEducationalProducts_Call{Call: _e.mock.On("ListEducationalProducts", ctx, params)} +} + +func (_c *Client_ListEducationalProducts_Call) Run(run func(ctx context.Context, params sravni.ListEducationProductsParams)) *Client_ListEducationalProducts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(sravni.ListEducationProductsParams)) + }) + return _c +} + +func (_c *Client_ListEducationalProducts_Call) Return(result sravni.ListEducationProductsResponse, err error) *Client_ListEducationalProducts_Call { + _c.Call.Return(result, err) + return _c +} + +func (_c *Client_ListEducationalProducts_Call) RunAndReturn(run func(context.Context, sravni.ListEducationProductsParams) (sravni.ListEducationProductsResponse, error)) *Client_ListEducationalProducts_Call { + _c.Call.Return(run) + return _c +} + +// ListEducationalProductsFilterCount provides a mock function with given fields: ctx, params +func (_m *Client) ListEducationalProductsFilterCount(ctx context.Context, params sravni.ListEducationProductsParams) (sravni.ProductsFilterCount, error) { + ret := _m.Called(ctx, params) + + if len(ret) == 0 { + panic("no return value specified for ListEducationalProductsFilterCount") + } + + var r0 sravni.ProductsFilterCount + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, sravni.ListEducationProductsParams) (sravni.ProductsFilterCount, error)); ok { + return rf(ctx, params) + } + if rf, ok := ret.Get(0).(func(context.Context, sravni.ListEducationProductsParams) sravni.ProductsFilterCount); ok { + r0 = rf(ctx, params) + } else { + r0 = ret.Get(0).(sravni.ProductsFilterCount) + } + + if rf, ok := ret.Get(1).(func(context.Context, sravni.ListEducationProductsParams) error); ok { + r1 = rf(ctx, params) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_ListEducationalProductsFilterCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEducationalProductsFilterCount' +type Client_ListEducationalProductsFilterCount_Call struct { + *mock.Call +} + +// ListEducationalProductsFilterCount is a helper method to define mock.On call +// - ctx context.Context +// - params sravni.ListEducationProductsParams +func (_e *Client_Expecter) ListEducationalProductsFilterCount(ctx interface{}, params interface{}) *Client_ListEducationalProductsFilterCount_Call { + return &Client_ListEducationalProductsFilterCount_Call{Call: _e.mock.On("ListEducationalProductsFilterCount", ctx, params)} +} + +func (_c *Client_ListEducationalProductsFilterCount_Call) Run(run func(ctx context.Context, params sravni.ListEducationProductsParams)) *Client_ListEducationalProductsFilterCount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(sravni.ListEducationProductsParams)) + }) + return _c +} + +func (_c *Client_ListEducationalProductsFilterCount_Call) Return(result sravni.ProductsFilterCount, err error) *Client_ListEducationalProductsFilterCount_Call { + _c.Call.Return(result, err) + return _c +} + +func (_c *Client_ListEducationalProductsFilterCount_Call) RunAndReturn(run func(context.Context, sravni.ListEducationProductsParams) (sravni.ProductsFilterCount, error)) *Client_ListEducationalProductsFilterCount_Call { + _c.Call.Return(run) + return _c +} + +// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewClient(t interface { + mock.TestingT + Cleanup(func()) +}) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/common/client/sravni/noop.go b/internal/common/client/sravni/noop.go index 28044c9..d694cb4 100644 --- a/internal/common/client/sravni/noop.go +++ b/internal/common/client/sravni/noop.go @@ -12,6 +12,6 @@ func (NoopClient) GetMainPageState() (*PageState, error) { return nil, errors.ErrNotImplemented } -func (NoopClient) ListEducationalProducts(context.Context, ListEducationProductsParams) (listEducationProductsResponse, error) { - return listEducationProductsResponse{}, errors.ErrNotImplemented +func (NoopClient) ListEducationalProducts(context.Context, ListEducationProductsParams) (ListEducationProductsResponse, error) { + return ListEducationProductsResponse{}, errors.ErrNotImplemented } diff --git a/internal/kurious/adapters/adapters.go b/internal/kurious/adapters/adapters.go index 72dae86..bc152a8 100644 --- a/internal/kurious/adapters/adapters.go +++ b/internal/kurious/adapters/adapters.go @@ -1,5 +1,7 @@ package adapters +import "strings" + type domainer[T any] interface { AsDomain() T } @@ -7,3 +9,16 @@ type domainer[T any] interface { func asDomainFunc[T any, U domainer[T]](in U) (out T) { return in.AsDomain() } + +func joinColumns(columns []string) string { + return strings.Join(columns, ",") +} + +func namedArgColumns(columns []string) string { + out := make([]string, len(columns)) + for i, col := range columns { + out[i] = ":" + col + } + + return joinColumns(out) +} diff --git a/internal/kurious/adapters/sqlite_learning_category_repository.go b/internal/kurious/adapters/sqlite_learning_category_repository.go index 9ac134b..3c98749 100644 --- a/internal/kurious/adapters/sqlite_learning_category_repository.go +++ b/internal/kurious/adapters/sqlite_learning_category_repository.go @@ -21,7 +21,7 @@ var ( "courses_count", } - learningCategoryColumnsStr = strings.Join(learningCategoryColumns, ",") + learningCategoryColumnsStr = joinColumns(learningCategoryColumns) ) type learningCategoryDB struct { diff --git a/internal/kurious/adapters/sqlite_learning_category_repository_test.go b/internal/kurious/adapters/sqlite_learning_category_repository_test.go index 1a7711b..9038cc3 100644 --- a/internal/kurious/adapters/sqlite_learning_category_repository_test.go +++ b/internal/kurious/adapters/sqlite_learning_category_repository_test.go @@ -76,6 +76,7 @@ func (s *sqliteLearningCategoriesRepositorySuite) TestUpsert() { repo := s.connection.LearningCategory() gotCategory, err := repo.Get(s.ctx, categoryID) s.ErrorIs(err, domain.ErrNotFound) + s.Empty(gotCategory) createdCategory := domain.LearningCategory{ ID: categoryID, diff --git a/internal/kurious/adapters/sqlite_organization.go b/internal/kurious/adapters/sqlite_organization.go deleted file mode 100644 index 4e9081d..0000000 --- a/internal/kurious/adapters/sqlite_organization.go +++ /dev/null @@ -1,34 +0,0 @@ -package adapters - -import ( - "database/sql" - "time" - - "git.loyso.art/frx/kurious/internal/kurious/domain" -) - -type organizationDB struct { - ID string `db:"id"` - ExternalID sql.NullString `db:"external_id"` - Alias string `db:"alias"` - Name string `db:"name"` - Site string `db:"site"` - Logo string `db:"logo"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - DeletedAt sql.NullTime `db:"deleted_at"` -} - -func (o *organizationDB) AsDomain() domain.Organization { - return domain.Organization{ - ID: o.ID, - ExternalID: nullStringAsDomain(o.ExternalID), - Alias: o.Alias, - Name: o.Name, - Site: o.Site, - LogoLink: o.Logo, - CreatedAt: o.CreatedAt, - UpdatedAt: o.UpdatedAt, - DeletedAt: nullTimeAsDomain(o.DeletedAt), - } -} diff --git a/internal/kurious/adapters/sqlite_organization_repository.go b/internal/kurious/adapters/sqlite_organization_repository.go new file mode 100644 index 0000000..d57f6b4 --- /dev/null +++ b/internal/kurious/adapters/sqlite_organization_repository.go @@ -0,0 +1,156 @@ +package adapters + +import ( + "context" + "database/sql" + "errors" + "fmt" + "log/slog" + "time" + + "git.loyso.art/frx/kurious/internal/kurious/domain" + + "github.com/jmoiron/sqlx" +) + +var ( + organizationColumns = []string{ + "id", + "external_id", + "alias", + "name", + "site", + "logo", + "created_at", + "updated_at", + "deleted_at", + } + + organizationColumnsStr = joinColumns(organizationColumns) + organizationColumnsArgsStr = namedArgColumns(organizationColumns) +) + +type organizationDB struct { + ID string `db:"id"` + ExternalID sql.NullString `db:"external_id"` + Alias string `db:"alias"` + Name string `db:"name"` + Site string `db:"site"` + Logo string `db:"logo"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + DeletedAt sql.NullTime `db:"deleted_at"` +} + +func (o *organizationDB) AsDomain() domain.Organization { + return domain.Organization{ + ID: o.ID, + ExternalID: nullStringAsDomain(o.ExternalID), + Alias: o.Alias, + Name: o.Name, + Site: o.Site, + LogoLink: o.Logo, + CreatedAt: o.CreatedAt, + UpdatedAt: o.UpdatedAt, + DeletedAt: nullTimeAsDomain(o.DeletedAt), + } +} + +func (o *organizationDB) FromDomain(in domain.Organization) { + *o = organizationDB{ + ID: in.ID, + ExternalID: nullableValueAsString(in.ExternalID), + Alias: in.Alias, + Name: in.Name, + Site: in.Site, + Logo: in.LogoLink, + CreatedAt: in.CreatedAt, + UpdatedAt: in.UpdatedAt, + DeletedAt: nullableValueAsTime(in.DeletedAt), + } +} + +func (c *sqliteConnection) Organization() domain.OrganizationRepository { + return &sqliteOrganizationRepository{ + db: c.db, + log: c.log.With("repository", "organization"), + } +} + +type sqliteOrganizationRepository struct { + db *sqlx.DB + log *slog.Logger +} + +func (r *sqliteOrganizationRepository) Get(ctx context.Context, params domain.GetOrganizationParams) (out domain.Organization, err error) { + const queryTemplate = "SELECT %s FROM organizations WHERE 1=1" + query := fmt.Sprintf(queryTemplate, organizationColumnsStr) + args := make([]any, 0, 2) + if params.ID.Valid() { + args = append(args, params.ID.Value()) + query += " AND id = ?" + } + if params.ExternalID.Valid() { + args = append(args, params.ExternalID.Value()) + query += " AND external_id = ?" + } + + var orgdb organizationDB + err = r.db.GetContext(ctx, &orgdb, query, args...) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return out, domain.ErrNotFound + } + return out, fmt.Errorf("executing query: %w", err) + } + + return orgdb.AsDomain(), nil +} + +func (r *sqliteOrganizationRepository) Create(ctx context.Context, params domain.CreateOrganizationParams) (out domain.Organization, err error) { + const queryTemplate = `INSERT INTO organizations (%[1]s) VALUES (%[2]s) RETURNING %[1]s` + query := fmt.Sprintf(queryTemplate, organizationColumnsStr, organizationColumnsArgsStr) + + stmt, err := r.db.PrepareNamedContext(ctx, query) + if err != nil { + return out, fmt.Errorf("preparing statement: %w", err) + } + + var orgdb organizationDB + + now := time.Now().UTC().Truncate(time.Second) + err = stmt.GetContext(ctx, &orgdb, organizationDB{ + ID: params.ID, + ExternalID: nullableValueAsString(params.ExternalID), + Alias: params.Alias, + Name: params.Name, + Site: params.Site, + Logo: params.LogoLink, + CreatedAt: now, + UpdatedAt: now, + DeletedAt: sql.NullTime{}, + }) + if err != nil { + return out, fmt.Errorf("executing query: %w", err) + } + + return orgdb.AsDomain(), nil +} + +func (r *sqliteOrganizationRepository) Delete(ctx context.Context, id string) error { + const query = `DELETE FROM organizations WHERE id = ?` + result, err := r.db.ExecContext(ctx, query, id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return domain.ErrNotFound + } + return fmt.Errorf("executing query: %w", err) + } + + affected, _ := result.RowsAffected() + if affected == 0 { + return domain.ErrNotFound + } + + return nil +} diff --git a/internal/kurious/adapters/sqlite_organization_repository_test.go b/internal/kurious/adapters/sqlite_organization_repository_test.go new file mode 100644 index 0000000..1f84fb0 --- /dev/null +++ b/internal/kurious/adapters/sqlite_organization_repository_test.go @@ -0,0 +1,71 @@ +package adapters + +import ( + "testing" + + "git.loyso.art/frx/kurious/internal/common/nullable" + "git.loyso.art/frx/kurious/internal/kurious/domain" + + "github.com/stretchr/testify/suite" +) + +func TestSqilteOrganizations(t *testing.T) { + suite.Run(t, new(sqliteOrganzationRepositorySuite)) +} + +type sqliteOrganzationRepositorySuite struct { + sqliteBaseSuite +} + +func (s *sqliteOrganzationRepositorySuite) TearDownTest() { + _ = s.connection.db.MustExecContext(s.ctx, "DELETE FROM organizations") +} + +func (s *sqliteOrganzationRepositorySuite) TestGet() { + var orgdb organizationDB + err := s.connection.db.GetContext( + s.ctx, &orgdb, + `INSERT INTO organizations (`+organizationColumnsStr+`)`+ + ` VALUES ("test-id", "test-ext-id", "test-alias", "test-name", "test-site", "test-logo", CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL)`+ + ` RETURNING `+organizationColumnsStr, + ) + s.NoError(err) + + expectedOrganization := orgdb.AsDomain() + + getParams := domain.GetOrganizationParams{ + ID: nullable.NewValue(orgdb.ID), + } + gotOrganization, err := s.connection.Organization().Get(s.ctx, getParams) + s.NoError(err) + + s.Equal(expectedOrganization, gotOrganization) +} + +func (s *sqliteOrganzationRepositorySuite) TestCreate() { + expectedOrganization := domain.Organization{ + ID: "test-id", + ExternalID: nullable.NewValue("test-ext-id"), + Alias: "test-alias", + Name: "test-name", + Site: "test-site", + LogoLink: "test-logo", + } + + gotOrganization, err := s.connection.Organization().Create(s.ctx, domain.CreateOrganizationParams{ + ID: expectedOrganization.ID, + ExternalID: expectedOrganization.ExternalID, + Alias: expectedOrganization.Alias, + Name: expectedOrganization.Name, + Site: expectedOrganization.Site, + LogoLink: expectedOrganization.LogoLink, + }) + s.NoError(err) + s.NotEmpty(gotOrganization.CreatedAt) + s.NotEmpty(gotOrganization.UpdatedAt) + s.Empty(gotOrganization.DeletedAt) + + expectedOrganization.CreatedAt = gotOrganization.CreatedAt + expectedOrganization.UpdatedAt = gotOrganization.UpdatedAt + s.Equal(expectedOrganization, gotOrganization) +} diff --git a/internal/kurious/domain/mocks/CourseRepository.go b/internal/kurious/domain/mocks/CourseRepository.go new file mode 100644 index 0000000..7666975 --- /dev/null +++ b/internal/kurious/domain/mocks/CourseRepository.go @@ -0,0 +1,534 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + domain "git.loyso.art/frx/kurious/internal/kurious/domain" + mock "github.com/stretchr/testify/mock" +) + +// CourseRepository is an autogenerated mock type for the CourseRepository type +type CourseRepository struct { + mock.Mock +} + +type CourseRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *CourseRepository) EXPECT() *CourseRepository_Expecter { + return &CourseRepository_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: _a0, _a1 +func (_m *CourseRepository) Create(_a0 context.Context, _a1 domain.CreateCourseParams) (domain.Course, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 domain.Course + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domain.CreateCourseParams) (domain.Course, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, domain.CreateCourseParams) domain.Course); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(domain.Course) + } + + if rf, ok := ret.Get(1).(func(context.Context, domain.CreateCourseParams) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CourseRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type CourseRepository_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 domain.CreateCourseParams +func (_e *CourseRepository_Expecter) Create(_a0 interface{}, _a1 interface{}) *CourseRepository_Create_Call { + return &CourseRepository_Create_Call{Call: _e.mock.On("Create", _a0, _a1)} +} + +func (_c *CourseRepository_Create_Call) Run(run func(_a0 context.Context, _a1 domain.CreateCourseParams)) *CourseRepository_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.CreateCourseParams)) + }) + return _c +} + +func (_c *CourseRepository_Create_Call) Return(_a0 domain.Course, _a1 error) *CourseRepository_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CourseRepository_Create_Call) RunAndReturn(run func(context.Context, domain.CreateCourseParams) (domain.Course, error)) *CourseRepository_Create_Call { + _c.Call.Return(run) + return _c +} + +// CreateBatch provides a mock function with given fields: _a0, _a1 +func (_m *CourseRepository) CreateBatch(_a0 context.Context, _a1 ...domain.CreateCourseParams) error { + _va := make([]interface{}, len(_a1)) + for _i := range _a1 { + _va[_i] = _a1[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for CreateBatch") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ...domain.CreateCourseParams) error); ok { + r0 = rf(_a0, _a1...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CourseRepository_CreateBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateBatch' +type CourseRepository_CreateBatch_Call struct { + *mock.Call +} + +// CreateBatch is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 ...domain.CreateCourseParams +func (_e *CourseRepository_Expecter) CreateBatch(_a0 interface{}, _a1 ...interface{}) *CourseRepository_CreateBatch_Call { + return &CourseRepository_CreateBatch_Call{Call: _e.mock.On("CreateBatch", + append([]interface{}{_a0}, _a1...)...)} +} + +func (_c *CourseRepository_CreateBatch_Call) Run(run func(_a0 context.Context, _a1 ...domain.CreateCourseParams)) *CourseRepository_CreateBatch_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]domain.CreateCourseParams, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(domain.CreateCourseParams) + } + } + run(args[0].(context.Context), variadicArgs...) + }) + return _c +} + +func (_c *CourseRepository_CreateBatch_Call) Return(_a0 error) *CourseRepository_CreateBatch_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CourseRepository_CreateBatch_Call) RunAndReturn(run func(context.Context, ...domain.CreateCourseParams) error) *CourseRepository_CreateBatch_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, id +func (_m *CourseRepository) Delete(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CourseRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type CourseRepository_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *CourseRepository_Expecter) Delete(ctx interface{}, id interface{}) *CourseRepository_Delete_Call { + return &CourseRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} +} + +func (_c *CourseRepository_Delete_Call) Run(run func(ctx context.Context, id string)) *CourseRepository_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *CourseRepository_Delete_Call) Return(_a0 error) *CourseRepository_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CourseRepository_Delete_Call) RunAndReturn(run func(context.Context, string) error) *CourseRepository_Delete_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function with given fields: ctx, id +func (_m *CourseRepository) Get(ctx context.Context, id string) (domain.Course, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 domain.Course + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (domain.Course, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) domain.Course); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(domain.Course) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CourseRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type CourseRepository_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *CourseRepository_Expecter) Get(ctx interface{}, id interface{}) *CourseRepository_Get_Call { + return &CourseRepository_Get_Call{Call: _e.mock.On("Get", ctx, id)} +} + +func (_c *CourseRepository_Get_Call) Run(run func(ctx context.Context, id string)) *CourseRepository_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *CourseRepository_Get_Call) Return(_a0 domain.Course, _a1 error) *CourseRepository_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CourseRepository_Get_Call) RunAndReturn(run func(context.Context, string) (domain.Course, error)) *CourseRepository_Get_Call { + _c.Call.Return(run) + return _c +} + +// GetByExternalID provides a mock function with given fields: ctx, id +func (_m *CourseRepository) GetByExternalID(ctx context.Context, id string) (domain.Course, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetByExternalID") + } + + var r0 domain.Course + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (domain.Course, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) domain.Course); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(domain.Course) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CourseRepository_GetByExternalID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByExternalID' +type CourseRepository_GetByExternalID_Call struct { + *mock.Call +} + +// GetByExternalID is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *CourseRepository_Expecter) GetByExternalID(ctx interface{}, id interface{}) *CourseRepository_GetByExternalID_Call { + return &CourseRepository_GetByExternalID_Call{Call: _e.mock.On("GetByExternalID", ctx, id)} +} + +func (_c *CourseRepository_GetByExternalID_Call) Run(run func(ctx context.Context, id string)) *CourseRepository_GetByExternalID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *CourseRepository_GetByExternalID_Call) Return(_a0 domain.Course, _a1 error) *CourseRepository_GetByExternalID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CourseRepository_GetByExternalID_Call) RunAndReturn(run func(context.Context, string) (domain.Course, error)) *CourseRepository_GetByExternalID_Call { + _c.Call.Return(run) + return _c +} + +// List provides a mock function with given fields: _a0, _a1 +func (_m *CourseRepository) List(_a0 context.Context, _a1 domain.ListCoursesParams) (domain.ListCoursesResult, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 domain.ListCoursesResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domain.ListCoursesParams) (domain.ListCoursesResult, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, domain.ListCoursesParams) domain.ListCoursesResult); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(domain.ListCoursesResult) + } + + if rf, ok := ret.Get(1).(func(context.Context, domain.ListCoursesParams) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CourseRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type CourseRepository_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 domain.ListCoursesParams +func (_e *CourseRepository_Expecter) List(_a0 interface{}, _a1 interface{}) *CourseRepository_List_Call { + return &CourseRepository_List_Call{Call: _e.mock.On("List", _a0, _a1)} +} + +func (_c *CourseRepository_List_Call) Run(run func(_a0 context.Context, _a1 domain.ListCoursesParams)) *CourseRepository_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.ListCoursesParams)) + }) + return _c +} + +func (_c *CourseRepository_List_Call) Return(_a0 domain.ListCoursesResult, _a1 error) *CourseRepository_List_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CourseRepository_List_Call) RunAndReturn(run func(context.Context, domain.ListCoursesParams) (domain.ListCoursesResult, error)) *CourseRepository_List_Call { + _c.Call.Return(run) + return _c +} + +// ListCourseThematics provides a mock function with given fields: _a0, _a1 +func (_m *CourseRepository) ListCourseThematics(_a0 context.Context, _a1 domain.ListCourseThematicsParams) (domain.ListCourseThematicsResult, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ListCourseThematics") + } + + var r0 domain.ListCourseThematicsResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domain.ListCourseThematicsParams) (domain.ListCourseThematicsResult, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, domain.ListCourseThematicsParams) domain.ListCourseThematicsResult); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(domain.ListCourseThematicsResult) + } + + if rf, ok := ret.Get(1).(func(context.Context, domain.ListCourseThematicsParams) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CourseRepository_ListCourseThematics_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListCourseThematics' +type CourseRepository_ListCourseThematics_Call struct { + *mock.Call +} + +// ListCourseThematics is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 domain.ListCourseThematicsParams +func (_e *CourseRepository_Expecter) ListCourseThematics(_a0 interface{}, _a1 interface{}) *CourseRepository_ListCourseThematics_Call { + return &CourseRepository_ListCourseThematics_Call{Call: _e.mock.On("ListCourseThematics", _a0, _a1)} +} + +func (_c *CourseRepository_ListCourseThematics_Call) Run(run func(_a0 context.Context, _a1 domain.ListCourseThematicsParams)) *CourseRepository_ListCourseThematics_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.ListCourseThematicsParams)) + }) + return _c +} + +func (_c *CourseRepository_ListCourseThematics_Call) Return(_a0 domain.ListCourseThematicsResult, _a1 error) *CourseRepository_ListCourseThematics_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CourseRepository_ListCourseThematics_Call) RunAndReturn(run func(context.Context, domain.ListCourseThematicsParams) (domain.ListCourseThematicsResult, error)) *CourseRepository_ListCourseThematics_Call { + _c.Call.Return(run) + return _c +} + +// ListLearningTypes provides a mock function with given fields: _a0 +func (_m *CourseRepository) ListLearningTypes(_a0 context.Context) (domain.ListLearningTypeResult, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for ListLearningTypes") + } + + var r0 domain.ListLearningTypeResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (domain.ListLearningTypeResult, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) domain.ListLearningTypeResult); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(domain.ListLearningTypeResult) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CourseRepository_ListLearningTypes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListLearningTypes' +type CourseRepository_ListLearningTypes_Call struct { + *mock.Call +} + +// ListLearningTypes is a helper method to define mock.On call +// - _a0 context.Context +func (_e *CourseRepository_Expecter) ListLearningTypes(_a0 interface{}) *CourseRepository_ListLearningTypes_Call { + return &CourseRepository_ListLearningTypes_Call{Call: _e.mock.On("ListLearningTypes", _a0)} +} + +func (_c *CourseRepository_ListLearningTypes_Call) Run(run func(_a0 context.Context)) *CourseRepository_ListLearningTypes_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *CourseRepository_ListLearningTypes_Call) Return(_a0 domain.ListLearningTypeResult, _a1 error) *CourseRepository_ListLearningTypes_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CourseRepository_ListLearningTypes_Call) RunAndReturn(run func(context.Context) (domain.ListLearningTypeResult, error)) *CourseRepository_ListLearningTypes_Call { + _c.Call.Return(run) + return _c +} + +// UpdateCourseDescription provides a mock function with given fields: ctx, id, description +func (_m *CourseRepository) UpdateCourseDescription(ctx context.Context, id string, description string) error { + ret := _m.Called(ctx, id, description) + + if len(ret) == 0 { + panic("no return value specified for UpdateCourseDescription") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, id, description) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CourseRepository_UpdateCourseDescription_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateCourseDescription' +type CourseRepository_UpdateCourseDescription_Call struct { + *mock.Call +} + +// UpdateCourseDescription is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - description string +func (_e *CourseRepository_Expecter) UpdateCourseDescription(ctx interface{}, id interface{}, description interface{}) *CourseRepository_UpdateCourseDescription_Call { + return &CourseRepository_UpdateCourseDescription_Call{Call: _e.mock.On("UpdateCourseDescription", ctx, id, description)} +} + +func (_c *CourseRepository_UpdateCourseDescription_Call) Run(run func(ctx context.Context, id string, description string)) *CourseRepository_UpdateCourseDescription_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *CourseRepository_UpdateCourseDescription_Call) Return(_a0 error) *CourseRepository_UpdateCourseDescription_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CourseRepository_UpdateCourseDescription_Call) RunAndReturn(run func(context.Context, string, string) error) *CourseRepository_UpdateCourseDescription_Call { + _c.Call.Return(run) + return _c +} + +// NewCourseRepository creates a new instance of CourseRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCourseRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *CourseRepository { + mock := &CourseRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/kurious/domain/mocks/LearningCategoryRepository.go b/internal/kurious/domain/mocks/LearningCategoryRepository.go new file mode 100644 index 0000000..7816a7c --- /dev/null +++ b/internal/kurious/domain/mocks/LearningCategoryRepository.go @@ -0,0 +1,199 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + domain "git.loyso.art/frx/kurious/internal/kurious/domain" + mock "github.com/stretchr/testify/mock" +) + +// LearningCategoryRepository is an autogenerated mock type for the LearningCategoryRepository type +type LearningCategoryRepository struct { + mock.Mock +} + +type LearningCategoryRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *LearningCategoryRepository) EXPECT() *LearningCategoryRepository_Expecter { + return &LearningCategoryRepository_Expecter{mock: &_m.Mock} +} + +// Get provides a mock function with given fields: _a0, _a1 +func (_m *LearningCategoryRepository) Get(_a0 context.Context, _a1 string) (domain.LearningCategory, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 domain.LearningCategory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (domain.LearningCategory, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, string) domain.LearningCategory); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(domain.LearningCategory) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// LearningCategoryRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type LearningCategoryRepository_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +func (_e *LearningCategoryRepository_Expecter) Get(_a0 interface{}, _a1 interface{}) *LearningCategoryRepository_Get_Call { + return &LearningCategoryRepository_Get_Call{Call: _e.mock.On("Get", _a0, _a1)} +} + +func (_c *LearningCategoryRepository_Get_Call) Run(run func(_a0 context.Context, _a1 string)) *LearningCategoryRepository_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *LearningCategoryRepository_Get_Call) Return(_a0 domain.LearningCategory, _a1 error) *LearningCategoryRepository_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *LearningCategoryRepository_Get_Call) RunAndReturn(run func(context.Context, string) (domain.LearningCategory, error)) *LearningCategoryRepository_Get_Call { + _c.Call.Return(run) + return _c +} + +// List provides a mock function with given fields: _a0 +func (_m *LearningCategoryRepository) List(_a0 context.Context) ([]domain.LearningCategory, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 []domain.LearningCategory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]domain.LearningCategory, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) []domain.LearningCategory); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]domain.LearningCategory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// LearningCategoryRepository_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type LearningCategoryRepository_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - _a0 context.Context +func (_e *LearningCategoryRepository_Expecter) List(_a0 interface{}) *LearningCategoryRepository_List_Call { + return &LearningCategoryRepository_List_Call{Call: _e.mock.On("List", _a0)} +} + +func (_c *LearningCategoryRepository_List_Call) Run(run func(_a0 context.Context)) *LearningCategoryRepository_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *LearningCategoryRepository_List_Call) Return(_a0 []domain.LearningCategory, _a1 error) *LearningCategoryRepository_List_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *LearningCategoryRepository_List_Call) RunAndReturn(run func(context.Context) ([]domain.LearningCategory, error)) *LearningCategoryRepository_List_Call { + _c.Call.Return(run) + return _c +} + +// Upsert provides a mock function with given fields: _a0, _a1 +func (_m *LearningCategoryRepository) Upsert(_a0 context.Context, _a1 domain.LearningCategory) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Upsert") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, domain.LearningCategory) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// LearningCategoryRepository_Upsert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Upsert' +type LearningCategoryRepository_Upsert_Call struct { + *mock.Call +} + +// Upsert is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 domain.LearningCategory +func (_e *LearningCategoryRepository_Expecter) Upsert(_a0 interface{}, _a1 interface{}) *LearningCategoryRepository_Upsert_Call { + return &LearningCategoryRepository_Upsert_Call{Call: _e.mock.On("Upsert", _a0, _a1)} +} + +func (_c *LearningCategoryRepository_Upsert_Call) Run(run func(_a0 context.Context, _a1 domain.LearningCategory)) *LearningCategoryRepository_Upsert_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.LearningCategory)) + }) + return _c +} + +func (_c *LearningCategoryRepository_Upsert_Call) Return(_a0 error) *LearningCategoryRepository_Upsert_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *LearningCategoryRepository_Upsert_Call) RunAndReturn(run func(context.Context, domain.LearningCategory) error) *LearningCategoryRepository_Upsert_Call { + _c.Call.Return(run) + return _c +} + +// NewLearningCategoryRepository creates a new instance of LearningCategoryRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewLearningCategoryRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *LearningCategoryRepository { + mock := &LearningCategoryRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/kurious/domain/mocks/OrganizationRepository.go b/internal/kurious/domain/mocks/OrganizationRepository.go new file mode 100644 index 0000000..dcfefd5 --- /dev/null +++ b/internal/kurious/domain/mocks/OrganizationRepository.go @@ -0,0 +1,198 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + domain "git.loyso.art/frx/kurious/internal/kurious/domain" + mock "github.com/stretchr/testify/mock" +) + +// OrganizationRepository is an autogenerated mock type for the OrganizationRepository type +type OrganizationRepository struct { + mock.Mock +} + +type OrganizationRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *OrganizationRepository) EXPECT() *OrganizationRepository_Expecter { + return &OrganizationRepository_Expecter{mock: &_m.Mock} +} + +// Create provides a mock function with given fields: _a0, _a1 +func (_m *OrganizationRepository) Create(_a0 context.Context, _a1 domain.CreateOrganizationParams) (domain.Organization, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 domain.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domain.CreateOrganizationParams) (domain.Organization, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, domain.CreateOrganizationParams) domain.Organization); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(domain.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, domain.CreateOrganizationParams) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrganizationRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type OrganizationRepository_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 domain.CreateOrganizationParams +func (_e *OrganizationRepository_Expecter) Create(_a0 interface{}, _a1 interface{}) *OrganizationRepository_Create_Call { + return &OrganizationRepository_Create_Call{Call: _e.mock.On("Create", _a0, _a1)} +} + +func (_c *OrganizationRepository_Create_Call) Run(run func(_a0 context.Context, _a1 domain.CreateOrganizationParams)) *OrganizationRepository_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.CreateOrganizationParams)) + }) + return _c +} + +func (_c *OrganizationRepository_Create_Call) Return(_a0 domain.Organization, _a1 error) *OrganizationRepository_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrganizationRepository_Create_Call) RunAndReturn(run func(context.Context, domain.CreateOrganizationParams) (domain.Organization, error)) *OrganizationRepository_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, id +func (_m *OrganizationRepository) Delete(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrganizationRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type OrganizationRepository_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *OrganizationRepository_Expecter) Delete(ctx interface{}, id interface{}) *OrganizationRepository_Delete_Call { + return &OrganizationRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} +} + +func (_c *OrganizationRepository_Delete_Call) Run(run func(ctx context.Context, id string)) *OrganizationRepository_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *OrganizationRepository_Delete_Call) Return(_a0 error) *OrganizationRepository_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrganizationRepository_Delete_Call) RunAndReturn(run func(context.Context, string) error) *OrganizationRepository_Delete_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function with given fields: _a0, _a1 +func (_m *OrganizationRepository) Get(_a0 context.Context, _a1 domain.GetOrganizationParams) (domain.Organization, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 domain.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, domain.GetOrganizationParams) (domain.Organization, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, domain.GetOrganizationParams) domain.Organization); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(domain.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, domain.GetOrganizationParams) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrganizationRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type OrganizationRepository_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 domain.GetOrganizationParams +func (_e *OrganizationRepository_Expecter) Get(_a0 interface{}, _a1 interface{}) *OrganizationRepository_Get_Call { + return &OrganizationRepository_Get_Call{Call: _e.mock.On("Get", _a0, _a1)} +} + +func (_c *OrganizationRepository_Get_Call) Run(run func(_a0 context.Context, _a1 domain.GetOrganizationParams)) *OrganizationRepository_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(domain.GetOrganizationParams)) + }) + return _c +} + +func (_c *OrganizationRepository_Get_Call) Return(_a0 domain.Organization, _a1 error) *OrganizationRepository_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrganizationRepository_Get_Call) RunAndReturn(run func(context.Context, domain.GetOrganizationParams) (domain.Organization, error)) *OrganizationRepository_Get_Call { + _c.Call.Return(run) + return _c +} + +// NewOrganizationRepository creates a new instance of OrganizationRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrganizationRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *OrganizationRepository { + mock := &OrganizationRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/kurious/domain/repository.go b/internal/kurious/domain/repository.go index ef6b10b..402db79 100644 --- a/internal/kurious/domain/repository.go +++ b/internal/kurious/domain/repository.go @@ -76,6 +76,11 @@ type CourseRepository interface { UpdateCourseDescription(ctx context.Context, id string, description string) error } +type GetOrganizationParams struct { + ID nullable.Value[string] + ExternalID nullable.Value[string] +} + type CreateOrganizationParams struct { ID string ExternalID nullable.Value[string] @@ -88,7 +93,7 @@ type CreateOrganizationParams struct { //go:generate mockery --name OrganizationRepository type OrganizationRepository interface { - Get(ctx context.Context) (Organization, error) + Get(context.Context, GetOrganizationParams) (Organization, error) Create(context.Context, CreateOrganizationParams) (Organization, error) Delete(ctx context.Context, id string) error } diff --git a/migrations/sqlite/002_learning_and_organization.sql b/migrations/sqlite/002_learning_and_organization.sql index b6f22de..cc29a6a 100644 --- a/migrations/sqlite/002_learning_and_organization.sql +++ b/migrations/sqlite/002_learning_and_organization.sql @@ -4,7 +4,7 @@ CREATE TABLE learning_categories ( courses_count INT NOT NULL DEFAULT 0 ); -create table organization ( +create table organizations ( id TEXT PRIMARY KEY, external_id TEXT NULL, alias TEXT NOT NULL, @@ -16,4 +16,4 @@ create table organization ( deleted_at DATETIME NULL ); -CREATE INDEX idx_organization_external_id ON organization (external_id); +CREATE INDEX idx_organization_external_id ON organizations (external_id);