From e9859f06baa841ff44f78841768e5806660e2fd3 Mon Sep 17 00:00:00 2001 From: "A. A. Sumitro" Date: Wed, 7 Dec 2022 15:57:03 +0800 Subject: [PATCH 1/2] WIP --- .github/workflows/lint.yml | 1 - cmd/api/main.go | 4 ++-- internal/catalog/ENTITY_DIAGRAM.md | 14 +++++--------- scripts/gen.sh | 21 +++++++++++++++++++++ scripts/run.sh | 2 ++ tmp/.gitignore | 1 - 6 files changed, 30 insertions(+), 13 deletions(-) create mode 100755 scripts/gen.sh delete mode 100644 tmp/.gitignore diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a33a24b..600ac14 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,7 +3,6 @@ name: Run Linter on: push: branches: - - main - dev pull_request: branches: diff --git a/cmd/api/main.go b/cmd/api/main.go index 032f4b5..b448860 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -65,10 +65,10 @@ func initEngine() { gin.SetMode(gin.ReleaseMode) } - accessLogFile, _ := os.Create("./tmp/access.log") + accessLogFile, _ := os.Create("./temps/access.log") gin.DefaultWriter = io.MultiWriter(accessLogFile, os.Stdout) - errorLogFile, _ := os.Create("./tmp/errors.log") + errorLogFile, _ := os.Create("./temps/errors.log") gin.DefaultErrorWriter = io.MultiWriter(errorLogFile, os.Stdout) appEngine = gin.Default() diff --git a/internal/catalog/ENTITY_DIAGRAM.md b/internal/catalog/ENTITY_DIAGRAM.md index 1efe337..99ec4ac 100644 --- a/internal/catalog/ENTITY_DIAGRAM.md +++ b/internal/catalog/ENTITY_DIAGRAM.md @@ -35,26 +35,22 @@ erDiagram string description int price } - - PRODUCTS_SUBCATEGORY { - int product_id - int subcategory_id - } PRODUCTS { int id int unit_id int category_id - string pic + int subcategory_id + string sku + string image json gallery string name string description int price } - + CATEGORIES ||--|{ SUBCATEGORIES: has_many - PRODUCTS }|--|{ PRODUCTS_SUBCATEGORY: many_to_many - SUBCATEGORIES }|--|{ PRODUCTS_SUBCATEGORY: many_to_many + PRODUCTS }|--|| SUBCATEGORIES: has_many PRODUCTS ||--|{ ADDONS: one_to_many PRODUCTS ||--|{ VARIANTS: one_to_many PRODUCTS }|--|| UNITS : one_to_many diff --git a/scripts/gen.sh b/scripts/gen.sh new file mode 100755 index 0000000..db40edc --- /dev/null +++ b/scripts/gen.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +FILE=.env +if test -f "$FILE"; then + set="abcdefghijklmonpqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + rand="" + for i in $(seq 1 64); do + char=${set:$RANDOM % ${#set}:1} + rand+=$char + done + echo $rand + +# TODO UPDATE .ENV JWT_SECRET_KEY +else + echo "===========================================================" + echo "| $FILE (environment) file does not exist. |" + echo "| Please Crete new .env file from .env.example. |" + echo "| by running this script: //:~$ cp .env.example .env |" + echo "===========================================================" + exit 0 +fi \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh index 6e6c1c3..4958cb9 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,3 +1,5 @@ +#!/bin/bash + FILE=.env if test -f "$FILE"; then echo "Everything is OK" diff --git a/tmp/.gitignore b/tmp/.gitignore deleted file mode 100644 index f59ec20..0000000 --- a/tmp/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* \ No newline at end of file From afc99130c924b2ad718b5102a5b2a90f2b993944 Mon Sep 17 00:00:00 2001 From: "A. A. Sumitro" Date: Wed, 7 Dec 2022 18:02:38 +0800 Subject: [PATCH 2/2] WIP --- ...21206065324_add_common_catalog_data.up.sql | 4 +- ...21207075900_create_table_products.down.sql | 1 + ...0221207075900_create_table_products.up.sql | 17 ++ ...75911_create_table_product_addons.down.sql | 1 + ...7075911_create_table_product_addons.up.sql | 10 + ...916_create_table_product_variants.down.sql | 2 + ...75916_create_table_product_variants.up.sql | 18 ++ .../20221207082217_add_product_data.up.sql | 17 ++ docs/docs.go | 280 ++++++++++++++++++ docs/swagger.json | 280 ++++++++++++++++++ docs/swagger.yaml | 179 +++++++++++ domain/catalog.go | 12 + domain/mocks/catalog_common_service.go | 91 ++++++ internal/catalog/ENTITY_DIAGRAM.md | 7 +- internal/catalog/catalog_module.go | 4 +- .../catalog/handler/http/addon_handler.go | 148 +++++++++ .../handler/http/addon_handler_test.go | 249 ++++++++++++++++ .../catalog/handler/http/variant_handler.go | 1 - .../repository/sql/addon_repository.go | 85 +++++- .../repository/sql/addon_repository_test.go | 177 +++++++++++ .../repository/sql/variant_repository.go | 7 - .../catalog/service/catalog_common_service.go | 41 +++ .../service/catalog_common_service_test.go | 214 +++++++++++-- internal/catalog/test.catalog.http | 33 +++ 24 files changed, 1836 insertions(+), 42 deletions(-) create mode 100644 db/migrations/20221207075900_create_table_products.down.sql create mode 100644 db/migrations/20221207075900_create_table_products.up.sql create mode 100644 db/migrations/20221207075911_create_table_product_addons.down.sql create mode 100644 db/migrations/20221207075911_create_table_product_addons.up.sql create mode 100644 db/migrations/20221207075916_create_table_product_variants.down.sql create mode 100644 db/migrations/20221207075916_create_table_product_variants.up.sql create mode 100644 db/migrations/20221207082217_add_product_data.up.sql create mode 100644 internal/catalog/handler/http/addon_handler_test.go delete mode 100644 internal/catalog/handler/http/variant_handler.go create mode 100644 internal/catalog/repository/sql/addon_repository_test.go delete mode 100644 internal/catalog/repository/sql/variant_repository.go diff --git a/db/migrations/20221206065324_add_common_catalog_data.up.sql b/db/migrations/20221206065324_add_common_catalog_data.up.sql index b14fa27..6903ebe 100644 --- a/db/migrations/20221206065324_add_common_catalog_data.up.sql +++ b/db/migrations/20221206065324_add_common_catalog_data.up.sql @@ -7,4 +7,6 @@ VALUES (1, 'meat'), (1, 'seafood'), (2, 'coffee'), (2, 'juice'); INSERT INTO units (magnitude, name, symbol) VALUES ('mass', 'gram', 'g'), ('mass', 'milligram', 'mg'), - ('mass', 'kilogram', 'kg'); + ('mass', 'kilogram', 'kg'), + ('mass', 'milliliter', 'ml'), + ('mass', 'liter', 'l'); diff --git a/db/migrations/20221207075900_create_table_products.down.sql b/db/migrations/20221207075900_create_table_products.down.sql new file mode 100644 index 0000000..39a3c0e --- /dev/null +++ b/db/migrations/20221207075900_create_table_products.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS products; diff --git a/db/migrations/20221207075900_create_table_products.up.sql b/db/migrations/20221207075900_create_table_products.up.sql new file mode 100644 index 0000000..6788a5c --- /dev/null +++ b/db/migrations/20221207075900_create_table_products.up.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS products ( + id BIGSERIAL PRIMARY KEY NOT NULL, + category_id BIGINT NOT NULL, + subcategory_id BIGINT NOT NULL, + sku VARCHAR(255) UNIQUE NOT NULL, + image VARCHAR(255), + gallery TEXT, + name VARCHAR(255) NOT NULL, + description VARCHAR(255), + price FLOAT NOT NULL +); + +ALTER TABLE products ADD CONSTRAINT fk_products_categories + FOREIGN KEY (category_id) REFERENCES categories(id); + +ALTER TABLE products ADD CONSTRAINT fk_products_subcategories + FOREIGN KEY (subcategory_id) REFERENCES subcategories(id); diff --git a/db/migrations/20221207075911_create_table_product_addons.down.sql b/db/migrations/20221207075911_create_table_product_addons.down.sql new file mode 100644 index 0000000..0d212e7 --- /dev/null +++ b/db/migrations/20221207075911_create_table_product_addons.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS addons; diff --git a/db/migrations/20221207075911_create_table_product_addons.up.sql b/db/migrations/20221207075911_create_table_product_addons.up.sql new file mode 100644 index 0000000..2b04b1a --- /dev/null +++ b/db/migrations/20221207075911_create_table_product_addons.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS addons( + id BIGSERIAL PRIMARY KEY NOT NULL, +-- product_id BIGINT NOT NULL, + name VARCHAR(255), + description VARCHAR(255), + price FLOAT +); + +-- ALTER TABLE product_addons ADD CONSTRAINT fk_products_product_addons +-- FOREIGN KEY (product_id) REFERENCES products(id); \ No newline at end of file diff --git a/db/migrations/20221207075916_create_table_product_variants.down.sql b/db/migrations/20221207075916_create_table_product_variants.down.sql new file mode 100644 index 0000000..bb2c00c --- /dev/null +++ b/db/migrations/20221207075916_create_table_product_variants.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS product_variants; +DROP TYPE IF EXISTS variant_types; \ No newline at end of file diff --git a/db/migrations/20221207075916_create_table_product_variants.up.sql b/db/migrations/20221207075916_create_table_product_variants.up.sql new file mode 100644 index 0000000..30b36df --- /dev/null +++ b/db/migrations/20221207075916_create_table_product_variants.up.sql @@ -0,0 +1,18 @@ +CREATE TYPE variant_types AS ENUM ('none', 'size'); + +CREATE TABLE IF NOT EXISTS product_variants( + id BIGSERIAL PRIMARY KEY NOT NULL, + product_id BIGINT NOT NULL, + unit_id BIGINT NOT NULL, + unit_size FLOAT, + type VARIANT_TYPES DEFAULT 'none', + name VARCHAR(255), + description VARCHAR(255), + price FLOAT +); + +ALTER TABLE product_variants ADD CONSTRAINT fk_products_product_variants + FOREIGN KEY (product_id) REFERENCES products(id); + +ALTER TABLE product_variants ADD CONSTRAINT fk_units_product_variants + FOREIGN KEY (unit_id) REFERENCES units(id); \ No newline at end of file diff --git a/db/migrations/20221207082217_add_product_data.up.sql b/db/migrations/20221207082217_add_product_data.up.sql new file mode 100644 index 0000000..cf1651e --- /dev/null +++ b/db/migrations/20221207082217_add_product_data.up.sql @@ -0,0 +1,17 @@ +INSERT INTO addons (name, description, price) +VALUES ('oat milk', 'replace', 1), + ('raw milk', 'replace', 1), + ('cheese', 'extra cheese', 1), + ('chocolate', 'extra chocolate', 1); + +INSERT INTO products (category_id, subcategory_id, sku, name, description, price) +VALUES (2, 4, 'JMGO100', 'mango juice', 'this sweet, tangy, and fruity tropical juice can be made using a blender, handheld blender, or a food processor in under 5 minutes.', 25), + (1, 2, 'WA5S100', 'wagyu a5 steak', 'The highest yield grade and meat quality grade for Wagyu beef is A5, where A represents the yield grade, and 5 represents the meat quality grade. A5 Wagyu beef denotes meat with ideal firmness and texture, coloring, yield, and beef marbling score.', 100); + +INSERT INTO product_variants (product_id, type, name, description, unit_id, unit_size, price) +VALUES (1, 'size', 's', 'small', 4, 250, 0), + (1, 'size', 'm', 'medium', 4, 480, 2), + (1, 'size', 'l', 'large', 4, 650, 3), + (1, 'size', 'xl', 'extra large', 5, 1.5, 1), + (2, 'size', 'half', 'half portion', 1, 250, 0), + (2, 'size', 'normal', 'normal portion', 1, 500, 100); diff --git a/docs/docs.go b/docs/docs.go index 4508af9..675d1c6 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -24,6 +24,264 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/v1/addons": { + "get": { + "description": "Get Addons List.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Addons" + ], + "summary": "Addons List", + "responses": { + "200": { + "description": "OK RESPOND", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.SuccessRespond" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Addon" + } + } + } + } + ] + } + }, + "401": { + "description": "UNAUTHORIZED RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "500": { + "description": "INTERNAL SERVER ERROR RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + } + } + }, + "post": { + "description": "Create new addon.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Addons" + ], + "summary": "Store addon Data", + "parameters": [ + { + "type": "string", + "description": "name", + "name": "name", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "price", + "name": "price", + "in": "formData", + "required": true + } + ], + "responses": { + "201": { + "description": "CREATED RESPOND", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.SuccessRespond" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/domain.Addon" + } + } + } + ] + } + }, + "401": { + "description": "UNAUTHORIZED RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "422": { + "description": "UNPROCESSABLE ENTITY RESPOND", + "schema": { + "$ref": "#/definitions/utils.ValidationErrorRespond" + } + }, + "500": { + "description": "INTERNAL SERVER ERROR RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + } + } + } + }, + "/v1/addons/{id}": { + "put": { + "description": "Update addon Data by ID.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Addons" + ], + "summary": "Update addon Data", + "parameters": [ + { + "type": "integer", + "description": "addon id", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name", + "name": "name", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "price", + "name": "price", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "CREATED RESPOND", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.SuccessRespond" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/domain.Addon" + } + } + } + ] + } + }, + "400": { + "description": "BAD REQUEST RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "401": { + "description": "UNAUTHORIZED RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "422": { + "description": "UNPROCESSABLE ENTITY RESPOND", + "schema": { + "$ref": "#/definitions/utils.ValidationErrorRespond" + } + }, + "500": { + "description": "INTERNAL SERVER ERROR RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + } + } + }, + "delete": { + "description": "Delete addon Data by ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Addons" + ], + "summary": "Delete addon Data", + "parameters": [ + { + "type": "integer", + "description": "category id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "NO CONTENT RESPOND" + }, + "400": { + "description": "BAD REQUEST RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "401": { + "description": "UNAUTHORIZED RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "500": { + "description": "INTERNAL SERVER ERROR RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + } + } + } + }, "/v1/categories": { "get": { "description": "Get Categories List.", @@ -2509,6 +2767,28 @@ const docTemplate = `{ } }, "definitions": { + "domain.Addon": { + "type": "object", + "required": [ + "description", + "name", + "price" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, "domain.Category": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index d2b5392..691517b 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -12,6 +12,264 @@ } }, "paths": { + "/v1/addons": { + "get": { + "description": "Get Addons List.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Addons" + ], + "summary": "Addons List", + "responses": { + "200": { + "description": "OK RESPOND", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.SuccessRespond" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Addon" + } + } + } + } + ] + } + }, + "401": { + "description": "UNAUTHORIZED RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "500": { + "description": "INTERNAL SERVER ERROR RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + } + } + }, + "post": { + "description": "Create new addon.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Addons" + ], + "summary": "Store addon Data", + "parameters": [ + { + "type": "string", + "description": "name", + "name": "name", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "price", + "name": "price", + "in": "formData", + "required": true + } + ], + "responses": { + "201": { + "description": "CREATED RESPOND", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.SuccessRespond" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/domain.Addon" + } + } + } + ] + } + }, + "401": { + "description": "UNAUTHORIZED RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "422": { + "description": "UNPROCESSABLE ENTITY RESPOND", + "schema": { + "$ref": "#/definitions/utils.ValidationErrorRespond" + } + }, + "500": { + "description": "INTERNAL SERVER ERROR RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + } + } + } + }, + "/v1/addons/{id}": { + "put": { + "description": "Update addon Data by ID.", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Addons" + ], + "summary": "Update addon Data", + "parameters": [ + { + "type": "integer", + "description": "addon id", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name", + "name": "name", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "price", + "name": "price", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "CREATED RESPOND", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/utils.SuccessRespond" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/domain.Addon" + } + } + } + ] + } + }, + "400": { + "description": "BAD REQUEST RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "401": { + "description": "UNAUTHORIZED RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "422": { + "description": "UNPROCESSABLE ENTITY RESPOND", + "schema": { + "$ref": "#/definitions/utils.ValidationErrorRespond" + } + }, + "500": { + "description": "INTERNAL SERVER ERROR RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + } + } + }, + "delete": { + "description": "Delete addon Data by ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Addons" + ], + "summary": "Delete addon Data", + "parameters": [ + { + "type": "integer", + "description": "category id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "NO CONTENT RESPOND" + }, + "400": { + "description": "BAD REQUEST RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "401": { + "description": "UNAUTHORIZED RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + }, + "500": { + "description": "INTERNAL SERVER ERROR RESPOND", + "schema": { + "$ref": "#/definitions/utils.ErrorRespond" + } + } + } + } + }, "/v1/categories": { "get": { "description": "Get Categories List.", @@ -2497,6 +2755,28 @@ } }, "definitions": { + "domain.Addon": { + "type": "object", + "required": [ + "description", + "name", + "price" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, "domain.Category": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 61fa499..cd242d2 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,4 +1,19 @@ definitions: + domain.Addon: + properties: + description: + type: string + id: + type: integer + name: + type: string + price: + type: number + required: + - description + - name + - price + type: object domain.Category: properties: id: @@ -218,6 +233,170 @@ info: name: MIT url: https://github.com/aasumitro/posbe/blob/main/LICENSE paths: + /v1/addons: + get: + consumes: + - application/json + description: Get Addons List. + produces: + - application/json + responses: + "200": + description: OK RESPOND + schema: + allOf: + - $ref: '#/definitions/utils.SuccessRespond' + - properties: + data: + items: + $ref: '#/definitions/domain.Addon' + type: array + type: object + "401": + description: UNAUTHORIZED RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + "500": + description: INTERNAL SERVER ERROR RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + summary: Addons List + tags: + - Addons + post: + consumes: + - multipart/form-data + description: Create new addon. + parameters: + - description: name + in: formData + name: name + required: true + type: string + - description: description + in: formData + name: description + required: true + type: string + - description: price + in: formData + name: price + required: true + type: string + produces: + - application/json + responses: + "201": + description: CREATED RESPOND + schema: + allOf: + - $ref: '#/definitions/utils.SuccessRespond' + - properties: + data: + $ref: '#/definitions/domain.Addon' + type: object + "401": + description: UNAUTHORIZED RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + "422": + description: UNPROCESSABLE ENTITY RESPOND + schema: + $ref: '#/definitions/utils.ValidationErrorRespond' + "500": + description: INTERNAL SERVER ERROR RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + summary: Store addon Data + tags: + - Addons + /v1/addons/{id}: + delete: + consumes: + - application/json + description: Delete addon Data by ID. + parameters: + - description: category id + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "204": + description: NO CONTENT RESPOND + "400": + description: BAD REQUEST RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + "401": + description: UNAUTHORIZED RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + "500": + description: INTERNAL SERVER ERROR RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + summary: Delete addon Data + tags: + - Addons + put: + consumes: + - multipart/form-data + description: Update addon Data by ID. + parameters: + - description: addon id + in: path + name: id + required: true + type: integer + - description: name + in: formData + name: name + required: true + type: string + - description: description + in: formData + name: description + required: true + type: string + - description: price + in: formData + name: price + required: true + type: string + produces: + - application/json + responses: + "200": + description: CREATED RESPOND + schema: + allOf: + - $ref: '#/definitions/utils.SuccessRespond' + - properties: + data: + $ref: '#/definitions/domain.Addon' + type: object + "400": + description: BAD REQUEST RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + "401": + description: UNAUTHORIZED RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + "422": + description: UNPROCESSABLE ENTITY RESPOND + schema: + $ref: '#/definitions/utils.ValidationErrorRespond' + "500": + description: INTERNAL SERVER ERROR RESPOND + schema: + $ref: '#/definitions/utils.ErrorRespond' + summary: Update addon Data + tags: + - Addons /v1/categories: get: consumes: diff --git a/domain/catalog.go b/domain/catalog.go index 7d0ea1e..80af5d5 100644 --- a/domain/catalog.go +++ b/domain/catalog.go @@ -22,6 +22,13 @@ type ( Symbol string `json:"symbol" form:"symbol" binding:"required"` // e.g: kg [m, s] } + Addon struct { + ID int `json:"id"` + Name string `json:"name" form:"name" binding:"required"` + Description string `json:"description" form:"description" binding:"required"` + Price float32 `json:"price" form:"price" binding:"required"` + } + ICatalogCommonService interface { UnitList() (units []*Unit, errData *utils.ServiceError) AddUnit(data *Unit) (units *Unit, errData *utils.ServiceError) @@ -37,6 +44,11 @@ type ( AddSubcategory(data *Subcategory) (units *Subcategory, errData *utils.ServiceError) EditSubcategory(data *Subcategory) (units *Subcategory, errData *utils.ServiceError) DeleteSubcategory(data *Subcategory) *utils.ServiceError + + AddonList() (units []*Addon, errData *utils.ServiceError) + AddAddon(data *Addon) (units *Addon, errData *utils.ServiceError) + EditAddon(data *Addon) (units *Addon, errData *utils.ServiceError) + DeleteAddon(data *Addon) *utils.ServiceError } ICatalogProductService interface { diff --git a/domain/mocks/catalog_common_service.go b/domain/mocks/catalog_common_service.go index 52bd10d..bf02465 100644 --- a/domain/mocks/catalog_common_service.go +++ b/domain/mocks/catalog_common_service.go @@ -14,6 +14,31 @@ type ICatalogCommonService struct { mock.Mock } +// AddAddon provides a mock function with given fields: data +func (_m *ICatalogCommonService) AddAddon(data *domain.Addon) (*domain.Addon, *utils.ServiceError) { + ret := _m.Called(data) + + var r0 *domain.Addon + if rf, ok := ret.Get(0).(func(*domain.Addon) *domain.Addon); ok { + r0 = rf(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.Addon) + } + } + + var r1 *utils.ServiceError + if rf, ok := ret.Get(1).(func(*domain.Addon) *utils.ServiceError); ok { + r1 = rf(data) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*utils.ServiceError) + } + } + + return r0, r1 +} + // AddCategory provides a mock function with given fields: data func (_m *ICatalogCommonService) AddCategory(data *domain.Category) (*domain.Category, *utils.ServiceError) { ret := _m.Called(data) @@ -89,6 +114,31 @@ func (_m *ICatalogCommonService) AddUnit(data *domain.Unit) (*domain.Unit, *util return r0, r1 } +// AddonList provides a mock function with given fields: +func (_m *ICatalogCommonService) AddonList() ([]*domain.Addon, *utils.ServiceError) { + ret := _m.Called() + + var r0 []*domain.Addon + if rf, ok := ret.Get(0).(func() []*domain.Addon); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*domain.Addon) + } + } + + var r1 *utils.ServiceError + if rf, ok := ret.Get(1).(func() *utils.ServiceError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*utils.ServiceError) + } + } + + return r0, r1 +} + // CategoryList provides a mock function with given fields: func (_m *ICatalogCommonService) CategoryList() ([]*domain.Category, *utils.ServiceError) { ret := _m.Called() @@ -114,6 +164,22 @@ func (_m *ICatalogCommonService) CategoryList() ([]*domain.Category, *utils.Serv return r0, r1 } +// DeleteAddon provides a mock function with given fields: data +func (_m *ICatalogCommonService) DeleteAddon(data *domain.Addon) *utils.ServiceError { + ret := _m.Called(data) + + var r0 *utils.ServiceError + if rf, ok := ret.Get(0).(func(*domain.Addon) *utils.ServiceError); ok { + r0 = rf(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ServiceError) + } + } + + return r0 +} + // DeleteCategory provides a mock function with given fields: data func (_m *ICatalogCommonService) DeleteCategory(data *domain.Category) *utils.ServiceError { ret := _m.Called(data) @@ -162,6 +228,31 @@ func (_m *ICatalogCommonService) DeleteUnit(data *domain.Unit) *utils.ServiceErr return r0 } +// EditAddon provides a mock function with given fields: data +func (_m *ICatalogCommonService) EditAddon(data *domain.Addon) (*domain.Addon, *utils.ServiceError) { + ret := _m.Called(data) + + var r0 *domain.Addon + if rf, ok := ret.Get(0).(func(*domain.Addon) *domain.Addon); ok { + r0 = rf(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.Addon) + } + } + + var r1 *utils.ServiceError + if rf, ok := ret.Get(1).(func(*domain.Addon) *utils.ServiceError); ok { + r1 = rf(data) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*utils.ServiceError) + } + } + + return r0, r1 +} + // EditCategory provides a mock function with given fields: data func (_m *ICatalogCommonService) EditCategory(data *domain.Category) (*domain.Category, *utils.ServiceError) { ret := _m.Called(data) diff --git a/internal/catalog/ENTITY_DIAGRAM.md b/internal/catalog/ENTITY_DIAGRAM.md index 99ec4ac..d3b2355 100644 --- a/internal/catalog/ENTITY_DIAGRAM.md +++ b/internal/catalog/ENTITY_DIAGRAM.md @@ -22,7 +22,6 @@ erDiagram ADDONS { int id - int product_id string name string description int price @@ -31,14 +30,15 @@ erDiagram VARIANTS { int id int product_id + int unit_id string name string description int price + float unit_size } PRODUCTS { int id - int unit_id int category_id int subcategory_id string sku @@ -51,9 +51,8 @@ erDiagram CATEGORIES ||--|{ SUBCATEGORIES: has_many PRODUCTS }|--|| SUBCATEGORIES: has_many - PRODUCTS ||--|{ ADDONS: one_to_many PRODUCTS ||--|{ VARIANTS: one_to_many - PRODUCTS }|--|| UNITS : one_to_many + VARIANTS }|--|| UNITS : one_to_many PRODUCTS }|--|| CATEGORIES : one_to_many ``` #### ADDONS: diff --git a/internal/catalog/catalog_module.go b/internal/catalog/catalog_module.go index e7048b9..fd17039 100644 --- a/internal/catalog/catalog_module.go +++ b/internal/catalog/catalog_module.go @@ -14,8 +14,9 @@ func InitCatalogModule(ctx context.Context, config *config.Config, router *gin.E unitRepository := repository.NewUnitSQLRepository(config.GetDbConn()) categoryRepository := repository.NewCategorySQLRepository(config.GetDbConn()) subcategoryRepository := repository.NewSubcategorySQLRepository(config.GetDbConn()) + addonRepository := repository.NewAddonSQLRepository(config.GetDbConn()) catalogCommonService := service.NewCatalogCommonService(ctx, unitRepository, - categoryRepository, subcategoryRepository) + categoryRepository, subcategoryRepository, addonRepository) routerGroup := router.Group("v1") protectedRouter := routerGroup. Use(middleware.Auth(config.JWTSecretKey)). @@ -24,4 +25,5 @@ func InitCatalogModule(ctx context.Context, config *config.Config, router *gin.E http.NewUnitHandler(catalogCommonService, protectedRouter) http.NewCategoryHandler(catalogCommonService, protectedRouter) http.NewSubcategoryHandler(catalogCommonService, protectedRouter) + http.NewAddonHandler(catalogCommonService, protectedRouter) } diff --git a/internal/catalog/handler/http/addon_handler.go b/internal/catalog/handler/http/addon_handler.go index d02cfda..6bea4ec 100644 --- a/internal/catalog/handler/http/addon_handler.go +++ b/internal/catalog/handler/http/addon_handler.go @@ -1 +1,149 @@ package http + +import ( + "github.com/aasumitro/posbe/domain" + "github.com/aasumitro/posbe/pkg/utils" + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +type addonHandler struct { + svc domain.ICatalogCommonService +} + +// addons godoc +// @Schemes +// @Summary Addons List +// @Description Get Addons List. +// @Tags Addons +// @Accept json +// @Produce json +// @Success 200 {object} utils.SuccessRespond{data=[]domain.Addon} "OK RESPOND" +// @Failure 401 {object} utils.ErrorRespond "UNAUTHORIZED RESPOND" +// @Failure 500 {object} utils.ErrorRespond "INTERNAL SERVER ERROR RESPOND" +// @Router /v1/addons [GET] +func (handler addonHandler) fetch(ctx *gin.Context) { + data, err := handler.svc.AddonList() + if err != nil { + utils.NewHttpRespond(ctx, err.Code, err.Message) + return + } + + utils.NewHttpRespond(ctx, http.StatusOK, data) +} + +// addons godoc +// @Schemes +// @Summary Store addon Data +// @Description Create new addon. +// @Tags Addons +// @Accept mpfd +// @Produce json +// @Param name formData string true "name" +// @Param description formData string true "description" +// @Param price formData string true "price" +// @Success 201 {object} utils.SuccessRespond{data=domain.Addon} "CREATED RESPOND" +// @Failure 401 {object} utils.ErrorRespond "UNAUTHORIZED RESPOND" +// @Failure 422 {object} utils.ValidationErrorRespond "UNPROCESSABLE ENTITY RESPOND" +// @Failure 500 {object} utils.ErrorRespond "INTERNAL SERVER ERROR RESPOND" +// @Router /v1/addons [POST] +func (handler addonHandler) store(ctx *gin.Context) { + var form domain.Addon + if err := ctx.ShouldBind(&form); err != nil { + utils.NewHttpRespond(ctx, http.StatusUnprocessableEntity, err.Error()) + return + } + + data, err := handler.svc.AddAddon(&form) + if err != nil { + utils.NewHttpRespond(ctx, err.Code, err.Message) + return + } + + utils.NewHttpRespond(ctx, http.StatusCreated, data) +} + +// addons godoc +// @Schemes +// @Summary Update addon Data +// @Description Update addon Data by ID. +// @Tags Addons +// @Accept mpfd +// @Produce json +// @Param id path int true "addon id" +// @Param name formData string true "name" +// @Param description formData string true "description" +// @Param price formData string true "price" +// @Success 200 {object} utils.SuccessRespond{data=domain.Addon} "CREATED RESPOND" +// @Failure 400 {object} utils.ErrorRespond "BAD REQUEST RESPOND" +// @Failure 401 {object} utils.ErrorRespond "UNAUTHORIZED RESPOND" +// @Failure 422 {object} utils.ValidationErrorRespond "UNPROCESSABLE ENTITY RESPOND" +// @Failure 500 {object} utils.ErrorRespond "INTERNAL SERVER ERROR RESPOND" +// @Router /v1/addons/{id} [PUT] +func (handler addonHandler) update(ctx *gin.Context) { + idParams := ctx.Param("id") + id, errParse := strconv.Atoi(idParams) + if errParse != nil { + utils.NewHttpRespond(ctx, + http.StatusBadRequest, + errParse.Error()) + return + } + + var form domain.Addon + if err := ctx.ShouldBind(&form); err != nil { + utils.NewHttpRespond(ctx, http.StatusUnprocessableEntity, err.Error()) + return + } + + form.ID = id + data, err := handler.svc.EditAddon(&form) + if err != nil { + utils.NewHttpRespond(ctx, err.Code, err.Message) + return + } + + utils.NewHttpRespond(ctx, http.StatusOK, data) +} + +// addons godoc +// @Schemes +// @Summary Delete addon Data +// @Description Delete addon Data by ID. +// @Tags Addons +// @Accept json +// @Produce json +// @Param id path int true "category id" +// @Success 204 "NO CONTENT RESPOND" +// @Failure 400 {object} utils.ErrorRespond "BAD REQUEST RESPOND" +// @Failure 401 {object} utils.ErrorRespond "UNAUTHORIZED RESPOND" +// @Failure 500 {object} utils.ErrorRespond "INTERNAL SERVER ERROR RESPOND" +// @Router /v1/addons/{id} [DELETE] +func (handler addonHandler) destroy(ctx *gin.Context) { + idParams := ctx.Param("id") + id, errParse := strconv.Atoi(idParams) + if errParse != nil { + utils.NewHttpRespond(ctx, + http.StatusBadRequest, + errParse.Error()) + return + } + data := domain.Addon{ID: id} + + err := handler.svc.DeleteAddon(&data) + if err != nil { + utils.NewHttpRespond(ctx, err.Code, err.Message) + return + } + + utils.NewHttpRespond(ctx, http.StatusNoContent, nil) +} + +func NewAddonHandler(svc domain.ICatalogCommonService, router gin.IRoutes) { + handler := addonHandler{svc: svc} + router.GET("/addons", handler.fetch) + router.POST("/addons", handler.store) + router.PUT("/addons/:id", handler.update) + router.DELETE("/addons/:id", handler.destroy) +} diff --git a/internal/catalog/handler/http/addon_handler_test.go b/internal/catalog/handler/http/addon_handler_test.go new file mode 100644 index 0000000..499b9e2 --- /dev/null +++ b/internal/catalog/handler/http/addon_handler_test.go @@ -0,0 +1,249 @@ +package http + +import ( + "encoding/json" + "github.com/aasumitro/posbe/domain" + "github.com/aasumitro/posbe/domain/mocks" + "github.com/aasumitro/posbe/pkg/utils" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "net/http" + "net/http/httptest" + "testing" +) + +type addonHandlerTestSuite struct { + suite.Suite + row *domain.Addon + rows []*domain.Addon +} + +func (suite *addonHandlerTestSuite) SetupSuite() { + suite.row = &domain.Addon{ID: 1, Name: "test", Description: "test", Price: 1} + suite.rows = []*domain.Addon{suite.row, {ID: 1, Name: "test 2", Description: "test 2", Price: 1}} +} + +func (suite *addonHandlerTestSuite) TestHandler_Fetch_ShouldSuccess() { + svc := new(mocks.ICatalogCommonService) + svc. + On("AddonList"). + Return(suite.rows, nil). + Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + addonHandler{svc: svc}.fetch(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusOK, writer.Code) + assert.Equal(suite.T(), http.StatusOK, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusOK), got.Status) +} +func (suite *addonHandlerTestSuite) TestHandler_Fetch_ShouldError() { + svc := new(mocks.ICatalogCommonService) + svc. + On("AddonList"). + Return(nil, &utils.ServiceError{ + Code: http.StatusInternalServerError, + Message: "UNEXPECTED_ERROR", + }).Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + addonHandler{svc: svc}.fetch(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusInternalServerError, writer.Code) + assert.Equal(suite.T(), http.StatusInternalServerError, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusInternalServerError), got.Status) + assert.Equal(suite.T(), "UNEXPECTED_ERROR", got.Data) +} + +func (suite *addonHandlerTestSuite) TestHandler_Store_ShouldSuccess() { + svc := new(mocks.ICatalogCommonService) + svc. + On("AddAddon", mock.Anything). + Return(suite.row, nil). + Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + utils.MockJsonRequest(ctx, "POST", "application/json", map[string]interface{}{ + "name": "lorem", + "description": "ipsum", + "price": 1, + }) + addonHandler{svc: svc}.store(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusCreated, writer.Code) + assert.Equal(suite.T(), http.StatusCreated, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusCreated), got.Status) +} +func (suite *addonHandlerTestSuite) TestHandler_Store_ShouldError_UnprocessableEntity() { + svc := new(mocks.ICatalogCommonService) + svc. + On("AddAddon", mock.Anything). + Return(suite.row, nil). + Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + utils.MockJsonRequest(ctx, "POST", "application/json", nil) + addonHandler{svc: svc}.store(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusUnprocessableEntity, writer.Code) + assert.Equal(suite.T(), http.StatusUnprocessableEntity, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusUnprocessableEntity), got.Status) +} +func (suite *addonHandlerTestSuite) TestHandler_Store_ShouldError_Internal() { + svc := new(mocks.ICatalogCommonService) + svc. + On("AddAddon", mock.Anything). + Return(nil, &utils.ServiceError{ + Code: http.StatusInternalServerError, + Message: "UNEXPECTED_ERROR", + }).Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + utils.MockJsonRequest(ctx, "POST", "application/json", map[string]interface{}{ + "name": "lorem", + "description": "ipsum", + "price": 1, + }) + addonHandler{svc: svc}.store(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusInternalServerError, writer.Code) + assert.Equal(suite.T(), http.StatusInternalServerError, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusInternalServerError), got.Status) +} + +func (suite *addonHandlerTestSuite) TestHandler_Update_ShouldSuccess() { + svc := new(mocks.ICatalogCommonService) + svc. + On("EditAddon", mock.Anything). + Return(suite.row, nil). + Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + ctx.Params = []gin.Param{{Key: "id", Value: "1"}} + utils.MockJsonRequest(ctx, "PUT", "application/json", map[string]interface{}{ + "name": "lorem", + "description": "ipsum", + "price": 1, + }) + addonHandler{svc: svc}.update(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusOK, writer.Code) + assert.Equal(suite.T(), http.StatusOK, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusOK), got.Status) +} +func (suite *addonHandlerTestSuite) TestHandler_Update_ShouldError_BadRequest() { + svc := new(mocks.ICatalogCommonService) + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + ctx.Params = []gin.Param{{Key: "id", Value: "asd123"}} + utils.MockJsonRequest(ctx, "PUT", "application/json", nil) + addonHandler{svc: svc}.update(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusBadRequest, writer.Code) + assert.Equal(suite.T(), http.StatusBadRequest, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusBadRequest), got.Status) + +} +func (suite *addonHandlerTestSuite) TestHandler_Update_ShouldError_UnprocessableEntity() { + svc := new(mocks.ICatalogCommonService) + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + ctx.Params = []gin.Param{{Key: "id", Value: "1"}} + utils.MockJsonRequest(ctx, "PUT", "application/json", nil) + addonHandler{svc: svc}.update(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusUnprocessableEntity, writer.Code) + assert.Equal(suite.T(), http.StatusUnprocessableEntity, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusUnprocessableEntity), got.Status) +} +func (suite *addonHandlerTestSuite) TestHandler_Update_ShouldError_Internal() { + svc := new(mocks.ICatalogCommonService) + svc.On("EditAddon", mock.Anything). + Return(nil, &utils.ServiceError{ + Code: http.StatusInternalServerError, + Message: "UNEXPECTED_ERROR", + }).Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + ctx.Params = []gin.Param{{Key: "id", Value: "1"}} + utils.MockJsonRequest(ctx, "PUT", "application/json", map[string]interface{}{ + "name": "lorem", + "description": "ipsum", + "price": 1, + }) + addonHandler{svc: svc}.update(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusInternalServerError, writer.Code) + assert.Equal(suite.T(), http.StatusInternalServerError, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusInternalServerError), got.Status) +} + +func (suite *addonHandlerTestSuite) TestHandler_Destroy_ShouldSuccess() { + svc := new(mocks.ICatalogCommonService) + svc. + On("DeleteAddon", mock.Anything). + Return(nil). + Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + ctx.Params = []gin.Param{{Key: "id", Value: "1"}} + utils.MockJsonRequest(ctx, "DELETE", "application/json", nil) + addonHandler{svc: svc}.destroy(ctx) + assert.Equal(suite.T(), http.StatusNoContent, writer.Code) +} +func (suite *addonHandlerTestSuite) TestHandler_Destroy_ShouldErrorInternal() { + svc := new(mocks.ICatalogCommonService) + svc.On("DeleteAddon", mock.Anything). + Return(&utils.ServiceError{ + Code: http.StatusInternalServerError, + Message: "UNEXPECTED_ERROR", + }).Once() + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + ctx.Params = []gin.Param{{Key: "id", Value: "1"}} + utils.MockJsonRequest(ctx, "DELETE", "application/json", nil) + addonHandler{svc: svc}.destroy(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusInternalServerError, writer.Code) + assert.Equal(suite.T(), http.StatusInternalServerError, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusInternalServerError), got.Status) +} +func (suite *addonHandlerTestSuite) TestHandler_Destroy_ShouldErrorBadRequest() { + svc := new(mocks.ICatalogCommonService) + writer := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(writer) + ctx.Request = &http.Request{Header: make(http.Header)} + ctx.Params = []gin.Param{{Key: "id", Value: "asd1"}} + utils.MockJsonRequest(ctx, "DELETE", "application/json", nil) + addonHandler{svc: svc}.destroy(ctx) + var got utils.SuccessRespond + _ = json.Unmarshal(writer.Body.Bytes(), &got) + assert.Equal(suite.T(), http.StatusBadRequest, writer.Code) + assert.Equal(suite.T(), http.StatusBadRequest, got.Code) + assert.Equal(suite.T(), http.StatusText(http.StatusBadRequest), got.Status) +} + +func TestAddonHandlerService(t *testing.T) { + suite.Run(t, new(addonHandlerTestSuite)) +} diff --git a/internal/catalog/handler/http/variant_handler.go b/internal/catalog/handler/http/variant_handler.go deleted file mode 100644 index d02cfda..0000000 --- a/internal/catalog/handler/http/variant_handler.go +++ /dev/null @@ -1 +0,0 @@ -package http diff --git a/internal/catalog/repository/sql/addon_repository.go b/internal/catalog/repository/sql/addon_repository.go index ef76e7c..6a5d718 100644 --- a/internal/catalog/repository/sql/addon_repository.go +++ b/internal/catalog/repository/sql/addon_repository.go @@ -1,7 +1,90 @@ package sql -import "database/sql" +import ( + "context" + "database/sql" + "github.com/aasumitro/posbe/domain" +) type AddonSQLRepository struct { Db *sql.DB } + +func (repo AddonSQLRepository) All(ctx context.Context) (data []*domain.Addon, err error) { + q := "SELECT * FROM addons" + rows, err := repo.Db.QueryContext(ctx, q) + if err != nil { + return nil, err + } + defer func(rows *sql.Rows) { _ = rows.Close() }(rows) + + for rows.Next() { + var addon domain.Addon + + if err := rows.Scan( + &addon.ID, &addon.Name, + &addon.Description, &addon.Price, + ); err != nil { + return nil, err + } + + data = append(data, &addon) + } + + return data, nil +} + +func (repo AddonSQLRepository) Find(ctx context.Context, key domain.FindWith, val any) (data *domain.Addon, err error) { + q := "SELECT * FROM addons WHERE id = $1 LIMIT 1" + row := repo.Db.QueryRowContext(ctx, q, val) + + data = &domain.Addon{} + if err := row.Scan( + &data.ID, &data.Name, + &data.Description, &data.Price, + ); err != nil { + return nil, err + } + + return data, nil +} + +func (repo AddonSQLRepository) Create(ctx context.Context, params *domain.Addon) (data *domain.Addon, err error) { + q := "INSERT INTO addons (name, description, price) VALUES ($1, $2, $3) RETURNING *" + row := repo.Db.QueryRowContext(ctx, q, params.Name, params.Description, params.Price) + + data = &domain.Addon{} + if err := row.Scan( + &data.ID, &data.Name, + &data.Description, &data.Price, + ); err != nil { + return nil, err + } + + return data, nil +} + +func (repo AddonSQLRepository) Update(ctx context.Context, params *domain.Addon) (data *domain.Addon, err error) { + q := "UPDATE addons SET name = $1, description = $2, price = $3 WHERE id = $4 RETURNING *" + row := repo.Db.QueryRowContext(ctx, q, params.Name, params.Description, params.Price, params.ID) + + data = &domain.Addon{} + if err := row.Scan( + &data.ID, &data.Name, + &data.Description, &data.Price, + ); err != nil { + return nil, err + } + + return data, nil +} + +func (repo AddonSQLRepository) Delete(ctx context.Context, params *domain.Addon) error { + q := "DELETE FROM addons WHERE id = $1" + _, err := repo.Db.ExecContext(ctx, q, params.ID) + return err +} + +func NewAddonSQLRepository(db *sql.DB) domain.ICRUDRepository[domain.Addon] { + return &AddonSQLRepository{Db: db} +} diff --git a/internal/catalog/repository/sql/addon_repository_test.go b/internal/catalog/repository/sql/addon_repository_test.go new file mode 100644 index 0000000..6825c26 --- /dev/null +++ b/internal/catalog/repository/sql/addon_repository_test.go @@ -0,0 +1,177 @@ +package sql_test + +import ( + "context" + "database/sql" + "errors" + "github.com/DATA-DOG/go-sqlmock" + "github.com/aasumitro/posbe/domain" + repoSql "github.com/aasumitro/posbe/internal/catalog/repository/sql" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "regexp" + "testing" +) + +type addonRepositoryTestSuite struct { + suite.Suite + mock sqlmock.Sqlmock + repo domain.ICRUDRepository[domain.Addon] +} + +func (suite *addonRepositoryTestSuite) SetupSuite() { + var ( + db *sql.DB + err error + ) + + db, suite.mock, err = sqlmock.New( + sqlmock.QueryMatcherOption( + sqlmock.QueryMatcherRegexp)) + require.NoError(suite.T(), err) + + suite.repo = repoSql.NewAddonSQLRepository(db) +} + +func (suite *addonRepositoryTestSuite) AfterTest(_, _ string) { + require.NoError(suite.T(), suite.mock.ExpectationsWereMet()) +} + +func (suite *addonRepositoryTestSuite) TestRepository_All_ExpectReturnRows() { + data := suite.mock. + NewRows([]string{"id", "name", "description", "price"}). + AddRow(1, "test", "test", 1). + AddRow(2, "test 2", "test 2", 1) + query := "SELECT * FROM addons" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta).WillReturnRows(data) + res, err := suite.repo.All(context.TODO()) + require.Nil(suite.T(), err) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), res) +} + +func (suite *addonRepositoryTestSuite) TestRepository_All_ExpectReturnErrorFromQuery() { + query := "SELECT * FROM addons" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta).WillReturnError(errors.New("")) + res, err := suite.repo.All(context.TODO()) + require.NotNil(suite.T(), err) + require.Nil(suite.T(), res) +} + +func (suite *addonRepositoryTestSuite) TestRepository_All_ExpectReturnErrorFromScan() { + data := suite.mock. + NewRows([]string{"id", "name", "description", "price"}). + AddRow(1, "test", "test", 1). + AddRow(nil, nil, nil, nil) + query := "SELECT * FROM addons" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta).WillReturnRows(data) + res, err := suite.repo.All(context.TODO()) + require.Nil(suite.T(), res) + require.NotNil(suite.T(), err) +} + +func (suite *addonRepositoryTestSuite) TestRepository_Find_ExpectReturnRow() { + data := suite.mock. + NewRows([]string{"id", "name", "description", "price"}). + AddRow(1, "test", "test", 1) + query := "SELECT * FROM addons WHERE id = $1 LIMIT 1" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta).WillReturnRows(data) + res, err := suite.repo.Find(context.TODO(), domain.FindWithId, 1) + require.Nil(suite.T(), err) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), res) +} + +func (suite *addonRepositoryTestSuite) TestRepository_Find_ExpectReturnError() { + data := suite.mock. + NewRows([]string{"id", "name", "description", "price"}). + AddRow(nil, nil, nil, nil) + query := "SELECT * FROM addons WHERE id = $1 LIMIT 1" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta).WillReturnRows(data) + res, err := suite.repo.Find(context.TODO(), domain.FindWithId, 1) + require.Nil(suite.T(), res) + require.NotNil(suite.T(), err) +} + +func (suite *addonRepositoryTestSuite) TestRepository_Created_ExpectSuccess() { + addon := &domain.Addon{ID: 1, Name: "test", Description: "test", Price: 1} + data := suite.mock. + NewRows([]string{"id", "name", "description", "price"}). + AddRow(1, "test", "test", 1) + query := "INSERT INTO addons (name, description, price) VALUES ($1, $2, $3) RETURNING *" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta). + WithArgs(addon.Name, addon.Description, addon.Price). + WillReturnRows(data). + WillReturnError(nil) + res, err := suite.repo.Create(context.TODO(), addon) + require.Nil(suite.T(), err) + require.NotNil(suite.T(), res) +} + +func (suite *addonRepositoryTestSuite) TestRepository_Created_ExpectError() { + addon := &domain.Addon{ID: 1, Name: "test", Description: "test", Price: 1} + data := suite.mock. + NewRows([]string{"id", "name", "description", "price"}). + AddRow(1, nil, nil, nil) + query := "INSERT INTO addons (name, description, price) VALUES ($1, $2, $3) RETURNING *" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta). + WithArgs(addon.Name, addon.Description, addon.Price). + WillReturnRows(data). + WillReturnError(nil) + res, err := suite.repo.Create(context.TODO(), addon) + require.Nil(suite.T(), res) + require.NotNil(suite.T(), err) +} + +func (suite *addonRepositoryTestSuite) TestRepository_Updated_ExpectSuccess() { + addon := &domain.Addon{ID: 1, Name: "test", Description: "test", Price: 1} + data := suite.mock. + NewRows([]string{"id", "name", "description", "price"}). + AddRow(1, "test", "test", 1) + query := "UPDATE addons SET name = $1, description = $2, price = $3 WHERE id = $4 RETURNING *" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta). + WithArgs(addon.Name, addon.Description, addon.Price, addon.ID). + WillReturnRows(data). + WillReturnError(nil) + res, err := suite.repo.Update(context.TODO(), addon) + require.Nil(suite.T(), err) + require.NotNil(suite.T(), res) +} + +func (suite *addonRepositoryTestSuite) TestRepository_Updated_ExpectError() { + addon := &domain.Addon{ID: 1, Name: "test", Description: "test", Price: 1} + data := suite.mock. + NewRows([]string{"id", "name", "description", "price"}). + AddRow(1, nil, nil, nil) + query := "UPDATE addons SET name = $1, description = $2, price = $3 WHERE id = $4 RETURNING *" + meta := regexp.QuoteMeta(query) + suite.mock.ExpectQuery(meta). + WithArgs(addon.Name, addon.Description, addon.Price, addon.ID). + WillReturnRows(data). + WillReturnError(nil) + res, err := suite.repo.Update(context.TODO(), addon) + require.Nil(suite.T(), res) + require.NotNil(suite.T(), err) +} + +func (suite *addonRepositoryTestSuite) TestRepository_Delete_ExpectSuccess() { + expectedQuery := regexp.QuoteMeta("DELETE FROM addons WHERE id = $1") + suite.mock.ExpectExec(expectedQuery). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(0, 1)) + data := &domain.Addon{ID: 1} + err := suite.repo.Delete(context.TODO(), data) + require.Nil(suite.T(), err) +} + +func TestAddonRepository(t *testing.T) { + suite.Run(t, new(addonRepositoryTestSuite)) +} diff --git a/internal/catalog/repository/sql/variant_repository.go b/internal/catalog/repository/sql/variant_repository.go deleted file mode 100644 index 2b731cf..0000000 --- a/internal/catalog/repository/sql/variant_repository.go +++ /dev/null @@ -1,7 +0,0 @@ -package sql - -import "database/sql" - -type VariantSQLRepository struct { - Db *sql.DB -} diff --git a/internal/catalog/service/catalog_common_service.go b/internal/catalog/service/catalog_common_service.go index c199485..018c2e6 100644 --- a/internal/catalog/service/catalog_common_service.go +++ b/internal/catalog/service/catalog_common_service.go @@ -12,6 +12,7 @@ type catalogCommonService struct { unitRepo domain.ICRUDRepository[domain.Unit] categoryRepo domain.ICRUDRepository[domain.Category] subcategoryRepo domain.ICRUDRepository[domain.Subcategory] + addonRepo domain.ICRUDRepository[domain.Addon] } func (service catalogCommonService) UnitList() (units []*domain.Unit, errData *utils.ServiceError) { @@ -128,16 +129,56 @@ func (service catalogCommonService) DeleteSubcategory(data *domain.Subcategory) return nil } +func (service catalogCommonService) AddonList() (units []*domain.Addon, errData *utils.ServiceError) { + data, err := service.addonRepo.All(service.ctx) + + return utils.ValidateDataRows[domain.Addon](data, err) +} + +func (service catalogCommonService) AddAddon(data *domain.Addon) (units *domain.Addon, errData *utils.ServiceError) { + data, err := service.addonRepo.Create(service.ctx, data) + + return utils.ValidateDataRow[domain.Addon](data, err) +} + +func (service catalogCommonService) EditAddon(data *domain.Addon) (units *domain.Addon, errData *utils.ServiceError) { + data, err := service.addonRepo.Update(service.ctx, data) + + return utils.ValidateDataRow[domain.Addon](data, err) +} + +func (service catalogCommonService) DeleteAddon(data *domain.Addon) *utils.ServiceError { + data, err := service.addonRepo.Find(service.ctx, domain.FindWithId, data.ID) + if err != nil { + return &utils.ServiceError{ + Code: http.StatusInternalServerError, + Message: err.Error(), + } + } + + err = service.addonRepo.Delete(service.ctx, data) + if err != nil { + return &utils.ServiceError{ + Code: http.StatusInternalServerError, + Message: err.Error(), + } + } + + return nil +} + func NewCatalogCommonService( ctx context.Context, unitRepo domain.ICRUDRepository[domain.Unit], categoryRepo domain.ICRUDRepository[domain.Category], subcategoryRepo domain.ICRUDRepository[domain.Subcategory], + addonRepo domain.ICRUDRepository[domain.Addon], ) domain.ICatalogCommonService { return &catalogCommonService{ ctx: ctx, unitRepo: unitRepo, categoryRepo: categoryRepo, subcategoryRepo: subcategoryRepo, + addonRepo: addonRepo, } } diff --git a/internal/catalog/service/catalog_common_service_test.go b/internal/catalog/service/catalog_common_service_test.go index f93dc51..5d5291a 100644 --- a/internal/catalog/service/catalog_common_service_test.go +++ b/internal/catalog/service/catalog_common_service_test.go @@ -23,6 +23,8 @@ type catalogCommonService struct { categories []*domain.Category subcategory *domain.Subcategory subcategories []*domain.Subcategory + addon *domain.Addon + addons []*domain.Addon svcErr *utils.ServiceError } @@ -33,6 +35,9 @@ func (suite *catalogCommonService) SetupSuite() { suite.categories = []*domain.Category{suite.category, {ID: 1, Name: "test 2"}} suite.subcategory = &domain.Subcategory{ID: 1, CategoryId: 1, Name: "test"} suite.subcategories = []*domain.Subcategory{suite.subcategory, {ID: 2, CategoryId: 1, Name: "test 2"}} + + suite.addon = &domain.Addon{ID: 1, Name: "test", Description: "test", Price: 1} + suite.addons = []*domain.Addon{suite.addon, {ID: 2, Name: "test 2", Description: "test 2", Price: 2}} suite.svcErr = &utils.ServiceError{Code: 500, Message: "UNEXPECTED"} } @@ -40,7 +45,8 @@ func (suite *catalogCommonService) SetupSuite() { func (suite *catalogCommonService) TestService_UnitList_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("All", mock.Anything). Return(suite.units, nil).Once() data, err := svc.UnitList() @@ -52,7 +58,8 @@ func (suite *catalogCommonService) TestService_UnitList_ShouldSuccess() { func (suite *catalogCommonService) TestService_UnitList_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("All", mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.UnitList() @@ -65,7 +72,8 @@ func (suite *catalogCommonService) TestService_UnitList_ShouldError() { func (suite *catalogCommonService) TestService_AddUnit_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Create", mock.Anything, mock.Anything). Return(suite.unit, nil).Once() data, err := svc.AddUnit(suite.unit) @@ -77,7 +85,8 @@ func (suite *catalogCommonService) TestService_AddUnit_ShouldSuccess() { func (suite *catalogCommonService) TestService_AddUnit_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Create", mock.Anything, mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.AddUnit(suite.unit) @@ -90,7 +99,8 @@ func (suite *catalogCommonService) TestService_AddUnit_ShouldError() { func (suite *catalogCommonService) TestService_EditUnit_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Update", mock.Anything, mock.Anything). Return(suite.unit, nil).Once() data, err := svc.EditUnit(suite.unit) @@ -102,7 +112,8 @@ func (suite *catalogCommonService) TestService_EditUnit_ShouldSuccess() { func (suite *catalogCommonService) TestService_EditUnit_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Update", mock.Anything, mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.EditUnit(suite.unit) @@ -115,7 +126,8 @@ func (suite *catalogCommonService) TestService_EditUnit_ShouldError() { func (suite *catalogCommonService) TestService_DeleteUnit_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Return(suite.units[1], nil).Once() @@ -129,7 +141,8 @@ func (suite *catalogCommonService) TestService_DeleteUnit_ShouldSuccess() { func (suite *catalogCommonService) TestService_DeleteUnit_ShouldErrorWhenFind() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Once(). @@ -142,7 +155,8 @@ func (suite *catalogCommonService) TestService_DeleteUnit_ShouldErrorWhenFind() func (suite *catalogCommonService) TestService_DeleteUnit_ShouldErrorWhenDelete() { repoMock := new(mocks.ICRUDRepository[domain.Unit]) svc := service.NewCatalogCommonService(context.TODO(), repoMock, - new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Category]), new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Return(suite.units[1], nil).Once() @@ -159,7 +173,8 @@ func (suite *catalogCommonService) TestService_DeleteUnit_ShouldErrorWhenDelete( func (suite *catalogCommonService) TestService_CategoryList_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("All", mock.Anything). Return(suite.categories, nil).Once() data, err := svc.CategoryList() @@ -171,7 +186,8 @@ func (suite *catalogCommonService) TestService_CategoryList_ShouldSuccess() { func (suite *catalogCommonService) TestService_CategoryList_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("All", mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.CategoryList() @@ -184,7 +200,8 @@ func (suite *catalogCommonService) TestService_CategoryList_ShouldError() { func (suite *catalogCommonService) TestService_AddCategory_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Create", mock.Anything, mock.Anything). Return(suite.category, nil).Once() data, err := svc.AddCategory(suite.category) @@ -196,7 +213,8 @@ func (suite *catalogCommonService) TestService_AddCategory_ShouldSuccess() { func (suite *catalogCommonService) TestService_AddCategory_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Create", mock.Anything, mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.AddCategory(suite.category) @@ -209,7 +227,8 @@ func (suite *catalogCommonService) TestService_AddCategory_ShouldError() { func (suite *catalogCommonService) TestService_EditCategory_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Update", mock.Anything, mock.Anything). Return(suite.category, nil).Once() data, err := svc.EditCategory(suite.category) @@ -221,7 +240,8 @@ func (suite *catalogCommonService) TestService_EditCategory_ShouldSuccess() { func (suite *catalogCommonService) TestService_EditCategory_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Update", mock.Anything, mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.EditCategory(suite.category) @@ -234,7 +254,8 @@ func (suite *catalogCommonService) TestService_EditCategory_ShouldError() { func (suite *catalogCommonService) TestService_DeleteCategory_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Return(suite.categories[1], nil).Once() @@ -248,7 +269,8 @@ func (suite *catalogCommonService) TestService_DeleteCategory_ShouldSuccess() { func (suite *catalogCommonService) TestService_DeleteCategory_ShouldErrorWhenFind() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Once(). @@ -261,7 +283,8 @@ func (suite *catalogCommonService) TestService_DeleteCategory_ShouldErrorWhenFin func (suite *catalogCommonService) TestService_DeleteCategory_ShouldErrorWhenDelete() { repoMock := new(mocks.ICRUDRepository[domain.Category]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory])) + new(mocks.ICRUDRepository[domain.Unit]), repoMock, new(mocks.ICRUDRepository[domain.Subcategory]), + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Return(suite.categories[1], nil).Once() @@ -278,7 +301,8 @@ func (suite *catalogCommonService) TestService_DeleteCategory_ShouldErrorWhenDel func (suite *catalogCommonService) TestService_SubcategoryList_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("All", mock.Anything). Return(suite.subcategories, nil).Once() data, err := svc.SubcategoryList() @@ -290,7 +314,8 @@ func (suite *catalogCommonService) TestService_SubcategoryList_ShouldSuccess() { func (suite *catalogCommonService) TestService_SubcategoryList_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("All", mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.SubcategoryList() @@ -303,7 +328,8 @@ func (suite *catalogCommonService) TestService_SubcategoryList_ShouldError() { func (suite *catalogCommonService) TestService_AddSubcategory_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Create", mock.Anything, mock.Anything). Return(suite.subcategory, nil).Once() data, err := svc.AddSubcategory(suite.subcategory) @@ -315,7 +341,8 @@ func (suite *catalogCommonService) TestService_AddSubcategory_ShouldSuccess() { func (suite *catalogCommonService) TestService_AddSubcategory_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Create", mock.Anything, mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.AddSubcategory(suite.subcategory) @@ -328,7 +355,8 @@ func (suite *catalogCommonService) TestService_AddSubcategory_ShouldError() { func (suite *catalogCommonService) TestService_EditSubcategory_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Update", mock.Anything, mock.Anything). Return(suite.subcategory, nil).Once() data, err := svc.EditSubcategory(suite.subcategory) @@ -340,7 +368,8 @@ func (suite *catalogCommonService) TestService_EditSubcategory_ShouldSuccess() { func (suite *catalogCommonService) TestService_EditSubcategory_ShouldError() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock.On("Update", mock.Anything, mock.Anything). Return(nil, errors.New("UNEXPECTED")).Once() data, err := svc.EditSubcategory(suite.subcategory) @@ -353,7 +382,8 @@ func (suite *catalogCommonService) TestService_EditSubcategory_ShouldError() { func (suite *catalogCommonService) TestService_DeleteSubcategory_ShouldSuccess() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Return(suite.subcategories[1], nil).Once() @@ -367,7 +397,8 @@ func (suite *catalogCommonService) TestService_DeleteSubcategory_ShouldSuccess() func (suite *catalogCommonService) TestService_DeleteSubcategory_ShouldErrorWhenFind() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Once(). @@ -380,7 +411,8 @@ func (suite *catalogCommonService) TestService_DeleteSubcategory_ShouldErrorWhen func (suite *catalogCommonService) TestService_DeleteSubcategory_ShouldErrorWhenDelete() { repoMock := new(mocks.ICRUDRepository[domain.Subcategory]) svc := service.NewCatalogCommonService(context.TODO(), - new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock) + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), repoMock, + new(mocks.ICRUDRepository[domain.Addon])) repoMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Return(suite.subcategories[1], nil).Once() @@ -393,6 +425,134 @@ func (suite *catalogCommonService) TestService_DeleteSubcategory_ShouldErrorWhen repoMock.AssertExpectations(suite.T()) } +// === Addon +func (suite *catalogCommonService) TestService_AddonList_ShouldSuccess() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock.On("All", mock.Anything). + Return(suite.addons, nil).Once() + data, err := svc.AddonList() + require.Nil(suite.T(), err) + require.NotNil(suite.T(), data) + require.Equal(suite.T(), data, suite.addons) + repoMock.AssertExpectations(suite.T()) +} +func (suite *catalogCommonService) TestService_AddonList_ShouldError() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock.On("All", mock.Anything). + Return(nil, errors.New("UNEXPECTED")).Once() + data, err := svc.AddonList() + require.Nil(suite.T(), data) + require.NotNil(suite.T(), err) + require.Equal(suite.T(), err, suite.svcErr) + repoMock.AssertExpectations(suite.T()) +} + +func (suite *catalogCommonService) TestService_AddAddon_ShouldSuccess() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock.On("Create", mock.Anything, mock.Anything). + Return(suite.addon, nil).Once() + data, err := svc.AddAddon(suite.addon) + require.Nil(suite.T(), err) + require.NotNil(suite.T(), data) + require.Equal(suite.T(), data, suite.addon) + repoMock.AssertExpectations(suite.T()) +} +func (suite *catalogCommonService) TestService_AddAddon_ShouldError() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock.On("Create", mock.Anything, mock.Anything). + Return(nil, errors.New("UNEXPECTED")).Once() + data, err := svc.AddAddon(suite.addon) + require.Nil(suite.T(), data) + require.NotNil(suite.T(), err) + require.Equal(suite.T(), err, suite.svcErr) + repoMock.AssertExpectations(suite.T()) +} + +func (suite *catalogCommonService) TestService_EditAddon_ShouldSuccess() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock.On("Update", mock.Anything, mock.Anything). + Return(suite.addon, nil).Once() + data, err := svc.EditAddon(suite.addon) + require.Nil(suite.T(), err) + require.NotNil(suite.T(), data) + require.Equal(suite.T(), data, suite.addon) + repoMock.AssertExpectations(suite.T()) +} +func (suite *catalogCommonService) TestService_EditAddon_ShouldError() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock.On("Update", mock.Anything, mock.Anything). + Return(nil, errors.New("UNEXPECTED")).Once() + data, err := svc.EditAddon(suite.addon) + require.Nil(suite.T(), data) + require.NotNil(suite.T(), err) + require.Equal(suite.T(), err, suite.svcErr) + repoMock.AssertExpectations(suite.T()) +} + +func (suite *catalogCommonService) TestService_DeleteAddon_ShouldSuccess() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock. + On("Find", mock.Anything, mock.Anything, mock.Anything). + Return(suite.addons[1], nil).Once() + repoMock. + On("Delete", mock.Anything, mock.Anything). + Return(nil).Once() + err := svc.DeleteAddon(suite.addon) + require.Nil(suite.T(), err) + repoMock.AssertExpectations(suite.T()) +} +func (suite *catalogCommonService) TestService_DeleteAddon_ShouldErrorWhenFind() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock. + On("Find", mock.Anything, mock.Anything, mock.Anything). + Once(). + Return(nil, errors.New("UNEXPECTED")) + err := svc.DeleteAddon(suite.addon) + require.NotNil(suite.T(), err) + require.Equal(suite.T(), err, suite.svcErr) + repoMock.AssertExpectations(suite.T()) +} +func (suite *catalogCommonService) TestService_DeleteAddon_ShouldErrorWhenDelete() { + repoMock := new(mocks.ICRUDRepository[domain.Addon]) + svc := service.NewCatalogCommonService(context.TODO(), + new(mocks.ICRUDRepository[domain.Unit]), new(mocks.ICRUDRepository[domain.Category]), + new(mocks.ICRUDRepository[domain.Subcategory]), repoMock) + repoMock. + On("Find", mock.Anything, mock.Anything, mock.Anything). + Return(suite.addon, nil).Once() + repoMock. + On("Delete", mock.Anything, mock.Anything). + Return(errors.New("UNEXPECTED")).Once() + err := svc.DeleteAddon(suite.addon) + require.NotNil(suite.T(), err) + require.Equal(suite.T(), err, suite.svcErr) + repoMock.AssertExpectations(suite.T()) +} + func TestCatalogCommonService(t *testing.T) { suite.Run(t, new(catalogCommonService)) } diff --git a/internal/catalog/test.catalog.http b/internal/catalog/test.catalog.http index b027546..212171f 100644 --- a/internal/catalog/test.catalog.http +++ b/internal/catalog/test.catalog.http @@ -97,3 +97,36 @@ Content-Type: application/json DELETE http://localhost:8000/v1/subcategories/6 Authorization: Bearer "TOKEN_HERE" +=== +### Addon END-Point +=== +### GET - fetch list of addons +GET http://localhost:8000/v1/addons +Authorization: Bearer "TOKEN_HERE" +accept: application/json + +### POST - store new addon +POST http://localhost:8000/v1/addons +Authorization: Bearer "TOKEN_HERE" +Content-Type: application/json + +{ + "name": "lorem", + "description": "ipsum", + "price": 1 +} + +### PUT - Update specified addons data +PUT http://localhost:8000/v1/addons/5 +Authorization: Bearer "TOKEN_HERE" +Content-Type: application/json + +{ + "name": "lorem", + "description": "ipsum", + "price": 1 +} + +### DELETE - Destroy specified addons data +DELETE http://localhost:8000/v1/addons/5 +Authorization: Bearer "TOKEN_HERE"