diff --git a/.github/workflows/create coverage badge.yml b/.github/workflows/create coverage badge.yml index 403d86ca..28b0640b 100644 --- a/.github/workflows/create coverage badge.yml +++ b/.github/workflows/create coverage badge.yml @@ -32,7 +32,7 @@ jobs: - name: Run Test run: | - go test -v hsfl.de/group6/hsfl-master-ai-cloud-engineering/... -covermode=count -coverprofile=coverage.out + go test -v hsfl.de/group6/hsfl-master-ai-cloud-engineering/... -covermode=count -coverprofile=coverage.out --tags=integration go tool cover -func=coverage.out -o=coverage.out - name: Go Coverage Badge # Pass the `coverage.out` output to this action diff --git a/.github/workflows/run-tests-http-proxy-service.yml b/.github/workflows/run-tests-http-proxy-service.yml index 67480fc5..e3720183 100644 --- a/.github/workflows/run-tests-http-proxy-service.yml +++ b/.github/workflows/run-tests-http-proxy-service.yml @@ -20,14 +20,6 @@ jobs: with: go-version: 1.21 - - name: Install dependencies - working-directory: ./src/http-proxy-service - run: go mod tidy - - - name: Build - working-directory: ./src/http-proxy-service - run: go build -v ./... - - name: Test Go Module run: | cd src/http-proxy-service diff --git a/.github/workflows/run-tests-lib-folder.yml b/.github/workflows/run-tests-lib-folder.yml index e4a36e59..2469302a 100644 --- a/.github/workflows/run-tests-lib-folder.yml +++ b/.github/workflows/run-tests-lib-folder.yml @@ -21,30 +21,6 @@ jobs: with: go-version: 1.21 - - name: Install dependencies for config - working-directory: ./lib/config - run: go mod tidy - - - name: Install dependencies for router - working-directory: ./lib/router - run: go mod tidy - - - name: Install dependencies for rpc - working-directory: ./lib/rpc - run: go mod tidy - - - name: Build config library - working-directory: ./lib/config - run: go build -v ./... - - - name: Build router library - working-directory: ./lib/router - run: go build -v ./... - - - name: Build rpc library - working-directory: ./lib/rpc - run: go build -v ./... - - name: Test Go config Module run: | cd lib/config diff --git a/.github/workflows/run-tests-load-balancer-service.yml b/.github/workflows/run-tests-load-balancer-service.yml index 2203fff4..e2abae27 100644 --- a/.github/workflows/run-tests-load-balancer-service.yml +++ b/.github/workflows/run-tests-load-balancer-service.yml @@ -20,14 +20,6 @@ jobs: with: go-version: 1.21 - - name: Install dependencies - working-directory: ./src/load-balancer-service - run: go mod tidy - - - name: Build - working-directory: ./src/load-balancer-service - run: go build -v ./... - - name: Test Go Module run: | cd src/load-balancer-service diff --git a/.github/workflows/run-tests-product-service.yml b/.github/workflows/run-tests-product-service.yml index 7f720070..55b02d34 100644 --- a/.github/workflows/run-tests-product-service.yml +++ b/.github/workflows/run-tests-product-service.yml @@ -20,18 +20,10 @@ jobs: with: go-version: 1.21 - - name: Install dependencies - working-directory: ./src/product-service - run: go mod tidy - - - name: Build - working-directory: ./src/product-service - run: go build -v ./... - - name: Test Go Module run: | cd src/product-service - go test ./... + go test ./... --tags=integration test_exit_code=$? # Capture the exit code of the go test command if [ $test_exit_code -eq 0 ]; then echo "Tests passed successfully." diff --git a/.github/workflows/run-tests-shoppinglist-service.yml b/.github/workflows/run-tests-shoppinglist-service.yml index b841f283..f6be0ecd 100644 --- a/.github/workflows/run-tests-shoppinglist-service.yml +++ b/.github/workflows/run-tests-shoppinglist-service.yml @@ -20,18 +20,10 @@ jobs: with: go-version: 1.21 - - name: Install dependencies - working-directory: ./src/shoppinglist-service - run: go mod tidy - - - name: Build - working-directory: ./src/shoppinglist-service - run: go build -v ./... - - name: Test Go Module run: | cd src/shoppinglist-service - go test ./... + go test ./... --tags=integration test_exit_code=$? # Capture the exit code of the go test command if [ $test_exit_code -eq 0 ]; then echo "Tests passed successfully." diff --git a/.github/workflows/run-tests-user-service.yml b/.github/workflows/run-tests-user-service.yml index 0d0f66f3..7a9a36cd 100644 --- a/.github/workflows/run-tests-user-service.yml +++ b/.github/workflows/run-tests-user-service.yml @@ -20,18 +20,10 @@ jobs: with: go-version: 1.21 - - name: Install dependencies - working-directory: ./src/user-service - run: go mod tidy - - - name: Build - working-directory: ./src/user-service - run: go build -v ./... - - name: Test Go Module run: | cd src/user-service - go test ./... + go test ./... --tags=integration test_exit_code=$? # Capture the exit code of the go test command if [ $test_exit_code -eq 0 ]; then echo "Tests passed successfully." diff --git a/.github/workflows/run-tests-web-service.yml b/.github/workflows/run-tests-web-service.yml index 50aef4d8..75a323ba 100644 --- a/.github/workflows/run-tests-web-service.yml +++ b/.github/workflows/run-tests-web-service.yml @@ -20,14 +20,6 @@ jobs: with: go-version: 1.21 - - name: Install dependencies - working-directory: ./src/web-service - run: go mod tidy - - - name: Build - working-directory: ./src/web-service - run: go build -v ./... - - name: Test Go Module run: | cd src/web-service diff --git a/.mockery.yaml b/.mockery.yaml index 666bab25..9c9f6591 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -30,3 +30,13 @@ packages: interfaces: Controller: Repository: + hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/auth: + config: + dir: "./src/user-service/auth/_mock" + interfaces: + TokenGenerator: + hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/crypto: + config: + dir: "./src/user-service/crypto/_mock" + interfaces: + Hasher: \ No newline at end of file diff --git a/README.md b/README.md index 870a63de..7d92190c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Price Whisper -![Coverage](https://img.shields.io/badge/Coverage-75.1%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-83.3%25-brightgreen) ![GitHub release (by tag)](https://img.shields.io/github/v/tag/onyxmoon/hsfl-master-ai-cloud-engineering.svg?sort=semver&label=Version&color=4ccc93d) [![Run tests (lib folder)](https://github.com/Onyxmoon/hsfl-master-ai-cloud-engineering/actions/workflows/run-tests-lib-folder.yml/badge.svg)](https://github.com/Onyxmoon/hsfl-master-ai-cloud-engineering/actions/workflows/run-tests-lib-folder.yml) [![Run tests (http proxy service)](https://github.com/Onyxmoon/hsfl-master-ai-cloud-engineering/actions/workflows/run-tests-http-proxy-service.yml/badge.svg)](https://github.com/Onyxmoon/hsfl-master-ai-cloud-engineering/actions/workflows/run-tests-http-proxy-service.yml) @@ -41,7 +41,8 @@ The complete application can be deployed within a kubernetes cluster. ```shell kubectl apply -R -f ./kubernetes/manifests ``` - +5. The main page can be accessed via the public port of the `HTTP Proxy Service` container. +Monitoring can be accessed via the Grafana public port. There is an example dashboard configuration file at [kubernetes/grafana](kubernetes/grafana/dashboard-config.json). ## Setup project in Docker ### Requirements - Docker Compose version v2.23.3 diff --git a/kubernetes/kubernetes-cluster/README.md b/kubernetes/kubernetes-cluster/README.md new file mode 100644 index 00000000..8fb21a5a --- /dev/null +++ b/kubernetes/kubernetes-cluster/README.md @@ -0,0 +1,24 @@ +# Kubernetes Cluster Setup + +This README guides you through the process of setting up a Kubernetes cluster with vms using Vagrant and Ansible. Ensure that you have all necessary dependencies installed, including Ansible, Vagrant, and any SSH tools needed. + +## Steps to Follow + +1. First, change to the `kubernetes-cluster` directory +2. Move your ssh public key into `kubernetes-cluster/ubuntu/vagrant/id_rsa.pub` +3. Start the vm deployment with `vagrant up` +4. Change, to the `ansible_playbooks` folder +5. Optional: Sometimes you have to specify the ansible config + ```shell + export ANSIBLE_CONFIG=./ansible.cfg + ``` +6. Run `ansible-playbook setup.yaml` to setup cluster + +> The first time the manifests are automatically deployed after commissioning, the distribution of the pods may not be uniform. + +## Additional Notes +Ensure that Ansible is properly installed and configured on your system. +Verify that the SSH key has the correct permissions and is recognized by your system. +If you encounter any issues, consult the official Kubernetes and Ansible documentation for troubleshooting steps. +Conclusion +Following these steps should successfully set up a Kubernetes cluster using Ansible. For more detailed instructions or advanced configurations, refer to the Kubernetes and Ansible official documentation. \ No newline at end of file diff --git a/kubernetes/manifests/price-whisper/database.yaml b/kubernetes/manifests/price-whisper/database.yaml index 737dd345..5d55e9b5 100644 --- a/kubernetes/manifests/price-whisper/database.yaml +++ b/kubernetes/manifests/price-whisper/database.yaml @@ -9,8 +9,8 @@ stringData: config.json: | [ { - "username": "username", - "password": "changeMe!", + "username": "db-username", + "password": "db-pw-changeMe!", "perms": [ "all" ] }, { @@ -20,6 +20,22 @@ stringData: ] --- apiVersion: v1 +kind: PersistentVolume +metadata: + name: database-data + annotations: + author: Gruppe 6 +spec: + capacity: + storage: 1Gi + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Delete + hostPath: + path: /var/price-whisper/database-data/ +--- +apiVersion: v1 kind: PersistentVolumeClaim metadata: name: database-data @@ -27,6 +43,7 @@ metadata: annotations: author: Gruppe 6 spec: + volumeName: database-data accessModes: - ReadWriteOnce resources: diff --git a/kubernetes/manifests/price-whisper/products.yaml b/kubernetes/manifests/price-whisper/products.yaml index 558e233a..c5c03f4c 100644 --- a/kubernetes/manifests/price-whisper/products.yaml +++ b/kubernetes/manifests/price-whisper/products.yaml @@ -6,8 +6,8 @@ metadata: annotations: author: Gruppe 6 stringData: - RQLITE_USER: "username" - RQLITE_PASSWORD: "changeMe!" + RQLITE_USER: "db-username" + RQLITE_PASSWORD: "db-pw-changeMe!" --- apiVersion: v1 kind: ConfigMap diff --git a/kubernetes/manifests/price-whisper/shoppinglists.yaml b/kubernetes/manifests/price-whisper/shoppinglists.yaml index affc7d9a..89fd5c9a 100644 --- a/kubernetes/manifests/price-whisper/shoppinglists.yaml +++ b/kubernetes/manifests/price-whisper/shoppinglists.yaml @@ -6,8 +6,8 @@ metadata: annotations: author: Gruppe 6 stringData: - RQLITE_USER: "username" - RQLITE_PASSWORD: "changeMe!" + RQLITE_USER: "db-username" + RQLITE_PASSWORD: "db-pw-changeMe!" --- apiVersion: v1 kind: ConfigMap diff --git a/kubernetes/manifests/price-whisper/test-data.yaml b/kubernetes/manifests/price-whisper/test-data.yaml index 6d9412f4..57a9f07c 100644 --- a/kubernetes/manifests/price-whisper/test-data.yaml +++ b/kubernetes/manifests/price-whisper/test-data.yaml @@ -6,8 +6,8 @@ metadata: annotations: author: Gruppe 6 stringData: - RQLITE_USER: "username" - RQLITE_PASSWORD: "changeMe!" + RQLITE_USER: "db-username" + RQLITE_PASSWORD: "db-pw-changeMe!" --- apiVersion: v1 kind: ConfigMap diff --git a/kubernetes/manifests/price-whisper/users.yaml b/kubernetes/manifests/price-whisper/users.yaml index 781e2d2b..91265be8 100644 --- a/kubernetes/manifests/price-whisper/users.yaml +++ b/kubernetes/manifests/price-whisper/users.yaml @@ -6,8 +6,8 @@ metadata: annotations: author: Gruppe 6 stringData: - RQLITE_USER: "username" - RQLITE_PASSWORD: "changeMe!" + RQLITE_USER: "db-username" + RQLITE_PASSWORD: "db-pw-changeMe!" JWT_PRIVATE_KEY: | # provide ecdsa private key as string or container local file path diff --git a/src/http-proxy-service/proxy/default_manager_test.go b/src/http-proxy-service/proxy/default_manager_test.go index b83973bf..eb23dfea 100644 --- a/src/http-proxy-service/proxy/default_manager_test.go +++ b/src/http-proxy-service/proxy/default_manager_test.go @@ -19,14 +19,11 @@ func TestNewDefaultManager(t *testing.T) { }, } - // Create a new manager manager := NewDefaultManager(config) if manager == nil { t.Error("Expected a non-nil manager, got nil") } - - // You can write more specific tests based on your requirements. } func TestDefaultManager_GetProxyRouter(t *testing.T) { @@ -64,8 +61,7 @@ func TestProxyServer(t *testing.T) { })) defer testService2.Close() - // Create a sample configuration for testing - config := &Config{ + sampleConfig := &Config{ ListenAddress: "localhost:8080", ProxyRoutes: []Route{ { @@ -81,109 +77,46 @@ func TestProxyServer(t *testing.T) { }, } - // Start a test HTTP server using the NewDefaultManager - proxyManager := NewDefaultManager(config) + proxyManager := NewDefaultManager(sampleConfig) testProxyServer := httptest.NewServer(proxyManager.GetProxyRouter()) defer testProxyServer.Close() // Test an HTTP request to Route1 - resp, err := testProxyServer.Client().Get(testProxyServer.URL + "/context1/") - if err != nil { - t.Errorf("HTTP GET to /context1/test failed: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode) - } - - // Check if the response body matches the expected value for Route1 - body, err := io.ReadAll(resp.Body) - if err != nil { - t.Errorf("Failed to read response body: %v", err) - } - if string(body) != want1 { - t.Errorf("Expected response body for Route1 to be %s, but got %s", want1, string(body)) - } - - // Test an HTTP request to Route2 - resp, err = testProxyServer.Client().Get(testProxyServer.URL + "/context2/") - if err != nil { - t.Errorf("HTTP GET to /context2/test failed: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode) - } - - // Check if the response body matches the expected value for Route2 - body, err = io.ReadAll(resp.Body) - if err != nil { - t.Errorf("Failed to read response body: %v", err) - } - if string(body) != want2 { - t.Errorf("Expected response body for Route2 to be %s, but got %s", want2, string(body)) - } -} - -/*func TestDefaultManagerNewHandler(t *testing.T) { - type fields struct { - proxies []*httputil.ReverseProxy - routing *router.Router - } - type args struct { - p *httputil.ReverseProxy - } - tests := []struct { - name string - fields fields - args args - want func(http.ResponseWriter, *http.Request) - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dp := defaultManager{ - proxies: tt.fields.proxies, - routing: tt.fields.routing, - } - if got := dp.newHandler(tt.args.p); !reflect.DeepEqual(got, tt.want) { - t.Errorf("newHandler() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestDefaultManagerNewProxy(t *testing.T) { - type fields struct { - proxies []*httputil.ReverseProxy - routing *router.Router - } - type args struct { - targetUrl string - } - tests := []struct { - name string - fields fields - args args - want *httputil.ReverseProxy - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dp := defaultManager{ - proxies: tt.fields.proxies, - routing: tt.fields.routing, - } - got, err := dp.newProxy(tt.args.targetUrl) - if (err != nil) != tt.wantErr { - t.Errorf("newProxy() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("newProxy() got = %v, want %v", got, tt.want) - } - }) - } + t.Run("HTTP request to Route1", func(t *testing.T) { + resp, err := testProxyServer.Client().Get(testProxyServer.URL + "/context1/") + if err != nil { + t.Errorf("HTTP GET to /context1/test failed: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode) + } + + // Check response body matches + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("Failed to read response body: %v", err) + } + if string(body) != want1 { + t.Errorf("Expected response body for Route1 to be %s, but got %s", want1, string(body)) + } + }) + + t.Run("HTTP request to Route2", func(t *testing.T) { + resp, err := testProxyServer.Client().Get(testProxyServer.URL + "/context2/") + if err != nil { + t.Errorf("HTTP GET to /context2/test failed: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status code %d, but got %d", http.StatusOK, resp.StatusCode) + } + + // Check response body matches + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("Failed to read response body: %v", err) + } + if string(body) != want2 { + t.Errorf("Expected response body for Route2 to be %s, but got %s", want2, string(body)) + } + }) } -*/ diff --git a/src/product-service/api/rpc/server_test.go b/src/product-service/api/rpc/server_test.go index 80ef9cfc..1de95437 100644 --- a/src/product-service/api/rpc/server_test.go +++ b/src/product-service/api/rpc/server_test.go @@ -3,15 +3,24 @@ package rpc import ( "context" "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gopkg.in/errgo.v2/errors" proto "hsfl.de/group6/hsfl-master-ai-cloud-engineering/lib/rpc/product" "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices" + pricesMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices/_mock" + priceModel "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices/model" "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/products" + productsMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/products/_mock" + productModel "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/products/model" "testing" ) func TestNewProductServiceServer(t *testing.T) { - var productRepo products.Repository = products.NewDemoRepository() - var priceRepo prices.Repository = prices.NewDemoRepository() + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo server := NewProductServiceServer(&productRepo, &priceRepo) @@ -21,8 +30,10 @@ func TestNewProductServiceServer(t *testing.T) { } func TestProductServiceServer_CreateProduct(t *testing.T) { - productRepo := products.GenerateExampleDemoRepository() - priceRepo := prices.GenerateExampleDemoRepository() + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo server := NewProductServiceServer(&productRepo, &priceRepo) ctx := context.Background() @@ -30,69 +41,449 @@ func TestProductServiceServer_CreateProduct(t *testing.T) { Product: &proto.Product{ Id: 123, Description: "Test", - Ean: "123456789", + Ean: "12345670", }, } - resp, err := server.CreateProduct(ctx, req) - if err != nil { - t.Errorf("Error creating product: %v", err) - } + t.Run("Successful create", func(t *testing.T) { + mockProductRepo.EXPECT().Create( + &productModel.Product{Id: 123, Description: "Test", Ean: "12345670"}). + Return( + &productModel.Product{Id: 123, Description: "Test", Ean: "12345670"}, nil).Once() - if resp.Product.Id != 123 { - t.Errorf("Expected product ID '123', got '%d'", resp.Product.Id) - } + resp, err := server.CreateProduct(ctx, req) + if err != nil { + t.Errorf("Error creating product: %v", err) + } + + if resp.Product.Id != 123 { + t.Errorf("Expected product ID '123', got '%d'", resp.Product.Id) + } + }) + + t.Run("Unsuccessful create", func(t *testing.T) { + mockProductRepo.EXPECT().Create( + &productModel.Product{Id: 123, Description: "Test", Ean: "12345670"}). + Return(nil, errors.New(products.ErrorProductAlreadyExists)).Once() + + _, err := server.CreateProduct(ctx, req) + if e, ok := status.FromError(err); ok { + if e.Code() != codes.Internal { + t.Errorf("expected error: %v", err) + } + } + }) } func TestProductServiceServer_GetProduct(t *testing.T) { - productRepo := products.GenerateExampleDemoRepository() - priceRepo := prices.GenerateExampleDemoRepository() + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) + + t.Run("Successful get product", func(t *testing.T) { + mockProductRepo.EXPECT().FindById(uint64(2)).Return( + &productModel.Product{Id: 2, Description: "Test", Ean: "12345670"}, nil).Once() + + ctx := context.Background() + getReq := &proto.GetProductRequest{Id: 2} + resp, err := server.GetProduct(ctx, getReq) + if err != nil { + t.Errorf("Error getting product: %v", err) + } + + if resp.Product.Id != 2 { + t.Errorf("Expected product ID '123', got '%d'", resp.Product.Id) + } + }) + + t.Run("Unsuccessful get product", func(t *testing.T) { + mockProductRepo.EXPECT().FindById(uint64(2)).Return(nil, errors.New(products.ErrorProductNotFound)).Once() + + ctx := context.Background() + getReq := &proto.GetProductRequest{Id: 2} + _, err := server.GetProduct(ctx, getReq) + if e, ok := status.FromError(err); ok { + if e.Code() != codes.NotFound { + t.Errorf("expected error: %v", err) + } + } + }) +} + +func TestProductServiceServer_GetAllProducts(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo server := NewProductServiceServer(&productRepo, &priceRepo) + products := []*productModel.Product{ + {Id: 1, Description: "Banane", Ean: "12345670"}, + {Id: 2, Description: "Apfel", Ean: "13828523"}, + } + + mockProductRepo.EXPECT().FindAll().Return(products, nil) + ctx := context.Background() - getReq := &proto.GetProductRequest{Id: 2} - resp, err := server.GetProduct(ctx, getReq) + getReq := &proto.GetAllProductsRequest{} + resp, err := server.GetAllProducts(ctx, getReq) if err != nil { - t.Errorf("Error getting product: %v", err) + t.Errorf("Error getting all products: %v", err) } - if resp.Product.Id != 2 { - t.Errorf("Expected product ID '123', got '%d'", resp.Product.Id) + for i, product := range products { + if product.Id != resp.Products[i].Id { + t.Errorf("Expected product ID '%d', got '%d'", product.Id, resp.Products[i].Id) + } + if product.Description != resp.Products[i].Description { + t.Errorf("Expected product Description '%s', got '%s'", product.Description, resp.Products[i].Description) + } + if product.Ean != resp.Products[i].Ean { + t.Errorf("Expected product Ean '%s', got '%s'", product.Ean, resp.Products[i].Ean) + } } } -func TestProductServiceServer_CreatePrice(t *testing.T) { +func TestProductServiceServer_UpdateProduct(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) -} + t.Run("Successful Update", func(t *testing.T) { + changedProduct := &productModel.Product{Id: 1, Description: "Erdbeere", Ean: "12345670"} -func TestProductServiceServer_DeletePrice(t *testing.T) { + mockProductRepo.EXPECT().Update(changedProduct).Return(changedProduct, nil).Once() + + ctx := context.Background() + updReq := &proto.UpdateProductRequest{Product: &proto.Product{ + Id: 1, + Description: "Erdbeere", + Ean: "12345670", + }} + resp, err := server.UpdateProduct(ctx, updReq) + if err != nil { + t.Errorf("Error getting all products: %v", err) + } + + if changedProduct.Id != resp.Product.Id { + t.Errorf("Expected product ID '%d', got '%d'", changedProduct.Id, resp.Product.Id) + } + if changedProduct.Description != resp.Product.Description { + t.Errorf("Expected product Description '%s', got '%s'", changedProduct.Description, resp.Product.Description) + } + if changedProduct.Ean != resp.Product.Ean { + t.Errorf("Expected product Ean '%s', got '%s'", changedProduct.Ean, resp.Product.Ean) + } + }) + t.Run("Unsuccessful Update", func(t *testing.T) { + changedProduct := &productModel.Product{Id: 1, Description: "Erdbeere", Ean: "12345670"} + mockProductRepo.EXPECT().Update(changedProduct).Return(nil, errors.New(products.ErrorProductUpdate)).Once() + + ctx := context.Background() + updReq := &proto.UpdateProductRequest{Product: &proto.Product{ + Id: 1, + Description: "Erdbeere", + Ean: "12345670", + }} + _, err := server.UpdateProduct(ctx, updReq) + if e, ok := status.FromError(err); ok { + if e.Code() != codes.Internal { + t.Errorf("expected error: %v", err) + } + } + }) } func TestProductServiceServer_DeleteProduct(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) + + t.Run("Successful Delete", func(t *testing.T) { + productToDelete := &productModel.Product{Id: 1} + + mockProductRepo.EXPECT().Delete(productToDelete).Return(nil).Once() + + ctx := context.Background() + delReq := &proto.DeleteProductRequest{Id: 1} + _, err := server.DeleteProduct(ctx, delReq) + if err != nil { + t.Errorf("Error getting deleting product: %v", err) + } + }) + + t.Run("Unsuccessful Delete", func(t *testing.T) { + productToDelete := &productModel.Product{Id: 1} + + mockProductRepo.EXPECT().Delete(productToDelete).Return(errors.New(products.ErrorProductDeletion)).Once() + + ctx := context.Background() + delReq := &proto.DeleteProductRequest{Id: 1} + _, err := server.DeleteProduct(ctx, delReq) + if e, ok := status.FromError(err); ok { + if e.Code() != codes.Internal { + t.Errorf("expected error: %v", err) + } + } + }) +} + +func TestProductServiceServer_CreatePrice(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) + + ctx := context.Background() + req := &proto.CreatePriceRequest{ + Price: &proto.Price{ + UserId: 1, + ProductId: 2, + Price: 2.99, + }, + } + + t.Run("Successful create", func(t *testing.T) { + mockPriceRepo.EXPECT().Create( + &priceModel.Price{UserId: 1, ProductId: 2, Price: 2.99}). + Return( + &priceModel.Price{UserId: 1, ProductId: 2, Price: 2.99}, nil).Once() + + resp, err := server.CreatePrice(ctx, req) + if err != nil { + t.Errorf("Error creating product: %v", err) + } + if resp.Price.UserId != 1 { + t.Errorf("Expected user ID '1', got '%d'", resp.Price.UserId) + } + + if resp.Price.ProductId != 2 { + t.Errorf("Expected product ID '2', got '%d'", resp.Price.ProductId) + } + + if resp.Price.Price != 2.99 { + t.Errorf("Expected price '2.99', got '%f'", resp.Price.Price) + } + }) + + t.Run("Unsuccessful create", func(t *testing.T) { + mockPriceRepo.EXPECT().Create( + &priceModel.Price{UserId: 1, ProductId: 2, Price: 2.99}). + Return(nil, errors.New(prices.ErrorPriceAlreadyExists)).Once() + + _, err := server.CreatePrice(ctx, req) + if e, ok := status.FromError(err); ok { + if e.Code() != codes.Internal { + t.Errorf("expected error: %v", err) + } + } + }) } func TestProductServiceServer_FindAllPrices(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) + + availablePrices := []*priceModel.Price{ + {UserId: 1, ProductId: 2, Price: 2.99}, + {UserId: 1, ProductId: 1, Price: 3.99}, + } + + mockPriceRepo.EXPECT().FindAll().Return(availablePrices, nil) + + ctx := context.Background() + getReq := &proto.FindAllPricesRequest{} + resp, err := server.FindAllPrices(ctx, getReq) + if err != nil { + t.Errorf("Error getting all products: %v", err) + } + for i, price := range availablePrices { + if price.UserId != resp.Price[i].UserId { + t.Errorf("Expected price UserID '%d', got '%d'", price.UserId, resp.Price[i].UserId) + } + if price.ProductId != resp.Price[i].ProductId { + t.Errorf("Expected price ProductID '%d', got '%d'", price.ProductId, resp.Price[i].ProductId) + } + if price.Price != resp.Price[i].Price { + t.Errorf("Expected price'%f', got '%f'", price.Price, resp.Price[i].Price) + } + } } func TestProductServiceServer_FindAllPricesFromUser(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) + + availablePrices := []*priceModel.Price{ + {UserId: 1, ProductId: 2, Price: 2.99}, + {UserId: 1, ProductId: 1, Price: 3.99}, + } + + mockPriceRepo.EXPECT().FindAllByUser(uint64(1)).Return(availablePrices, nil) + ctx := context.Background() + getReq := &proto.FindAllPricesFromUserRequest{UserId: 1} + resp, err := server.FindAllPricesFromUser(ctx, getReq) + if err != nil { + t.Errorf("Error getting all products: %v", err) + } + + for i, price := range availablePrices { + if price.UserId != resp.Price[i].UserId { + t.Errorf("Expected price UserID '%d', got '%d'", price.UserId, resp.Price[i].UserId) + } + if price.ProductId != resp.Price[i].ProductId { + t.Errorf("Expected price ProductID '%d', got '%d'", price.ProductId, resp.Price[i].ProductId) + } + if price.Price != resp.Price[i].Price { + t.Errorf("Expected price'%f', got '%f'", price.Price, resp.Price[i].Price) + } + } } func TestProductServiceServer_FindPrice(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) -} + t.Run("Successful find Price", func(t *testing.T) { + mockPriceRepo.EXPECT().FindByIds(uint64(1), uint64(1)).Return( + &priceModel.Price{UserId: 1, ProductId: 1, Price: 3.99}, nil).Once() -func TestProductServiceServer_GetAllProducts(t *testing.T) { + ctx := context.Background() + getReq := &proto.FindPriceRequest{UserId: 1, ProductId: 1} + resp, err := server.FindPrice(ctx, getReq) + if err != nil { + t.Errorf("Error getting product: %v", err) + } + if resp.Price.UserId != 1 { + t.Errorf("Expected User ID '1', got '%d'", resp.Price.UserId) + } + if resp.Price.ProductId != 1 { + t.Errorf("Expected Product ID '1', got '%d'", resp.Price.ProductId) + } + }) + + t.Run("Unsuccessful find Price", func(t *testing.T) { + mockPriceRepo.EXPECT().FindByIds(uint64(1), uint64(5)).Return(nil, errors.New(prices.ErrorPriceNotFound)).Once() + + ctx := context.Background() + getReq := &proto.FindPriceRequest{UserId: 5, ProductId: 1} + _, err := server.FindPrice(ctx, getReq) + if e, ok := status.FromError(err); ok { + if e.Code() != codes.NotFound { + t.Errorf("expected error: %v", err) + } + } + }) } func TestProductServiceServer_UpdatePrice(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) + + t.Run("Successful Update", func(t *testing.T) { + changedPrice := &priceModel.Price{UserId: 1, ProductId: 1, Price: 5.55} + + mockPriceRepo.EXPECT().Update(changedPrice).Return(changedPrice, nil).Once() + + ctx := context.Background() + updReq := &proto.UpdatePriceRequest{Price: &proto.Price{ + UserId: 1, + ProductId: 1, + Price: 5.55, + }} + resp, err := server.UpdatePrice(ctx, updReq) + if err != nil { + t.Errorf("Error getting all products: %v", err) + } + + if changedPrice.UserId != resp.Price.UserId { + t.Errorf("Expected price UserID '%d', got '%d'", changedPrice.UserId, resp.Price.UserId) + } + if changedPrice.ProductId != resp.Price.ProductId { + t.Errorf("Expected price ProductID '%d', got '%d'", changedPrice.ProductId, resp.Price.ProductId) + } + if changedPrice.Price != resp.Price.Price { + t.Errorf("Expected price '%f', got '%f'", changedPrice.Price, resp.Price.Price) + } + }) + + t.Run("Unsuccessful Update", func(t *testing.T) { + changedPrice := &priceModel.Price{UserId: 4, ProductId: 1, Price: 5.55} + mockPriceRepo.EXPECT().Update(changedPrice).Return(nil, errors.New(prices.ErrorPriceUpdate)).Once() + + ctx := context.Background() + updReq := &proto.UpdatePriceRequest{Price: &proto.Price{ + UserId: 4, + ProductId: 1, + Price: 5.55, + }} + _, err := server.UpdatePrice(ctx, updReq) + if e, ok := status.FromError(err); ok { + if e.Code() != codes.Internal { + t.Errorf("expected error: %v", err) + } + } + }) } -func TestProductServiceServer_UpdateProduct(t *testing.T) { +func TestProductServiceServer_DeletePrice(t *testing.T) { + mockProductRepo := productsMock.NewMockRepository(t) + mockPriceRepo := pricesMock.NewMockRepository(t) + var productRepo products.Repository = mockProductRepo + var priceRepo prices.Repository = mockPriceRepo + server := NewProductServiceServer(&productRepo, &priceRepo) + + t.Run("Successful Delete", func(t *testing.T) { + priceToDelete := &priceModel.Price{UserId: 1, ProductId: 1} + + mockPriceRepo.EXPECT().Delete(priceToDelete).Return(nil).Once() + + ctx := context.Background() + delReq := &proto.DeletePriceRequest{UserId: 1, ProductId: 1} + _, err := server.DeletePrice(ctx, delReq) + if err != nil { + t.Errorf("Error getting deleting price: %v", err) + } + }) + + t.Run("Unsuccessful Delete", func(t *testing.T) { + priceToDelete := &priceModel.Price{UserId: 1, ProductId: 1} + + mockPriceRepo.EXPECT().Delete(priceToDelete).Return(errors.New(products.ErrorProductDeletion)).Once() + + ctx := context.Background() + delReq := &proto.DeletePriceRequest{UserId: 1, ProductId: 1} + _, err := server.DeletePrice(ctx, delReq) + if e, ok := status.FromError(err); ok { + if e.Code() != codes.Internal { + t.Errorf("expected error: %v", err) + } + } + }) } diff --git a/src/product-service/prices/default_controller.go b/src/product-service/prices/default_controller.go deleted file mode 100644 index 14ca63c5..00000000 --- a/src/product-service/prices/default_controller.go +++ /dev/null @@ -1,159 +0,0 @@ -package prices - -import ( - "encoding/json" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices/model" - "net/http" - "strconv" -) - -type DefaultController struct { - priceRepository Repository -} - -func NewDefaultController(priceRepository Repository) *DefaultController { - return &DefaultController{priceRepository} -} - -func (controller *DefaultController) GetPrices(writer http.ResponseWriter, request *http.Request) { - values, err := controller.priceRepository.FindAll() - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - } - - writer.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(writer).Encode(values) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - return - } -} - -func (controller *DefaultController) GetPricesByUser(writer http.ResponseWriter, request *http.Request) { - userId, err := strconv.ParseUint(request.Context().Value("userId").(string), 10, 64) - - if err != nil { - http.Error(writer, "Invalid userId", http.StatusBadRequest) - return - } - values, err := controller.priceRepository.FindAllByUser(userId) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - } - - writer.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(writer).Encode(values) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - return - } -} - -func (controller *DefaultController) GetPricesByProduct(writer http.ResponseWriter, request *http.Request) { - productId, err := strconv.ParseUint(request.Context().Value("productId").(string), 10, 64) - - if err != nil { - http.Error(writer, "Invalid productId", http.StatusBadRequest) - return - } - values, err := controller.priceRepository.FindAllByProduct(productId) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - } - - writer.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(writer).Encode(values) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - return - } -} - -func (controller *DefaultController) PostPrice(writer http.ResponseWriter, request *http.Request) { - productId, productIdErr := strconv.ParseUint(request.Context().Value("productId").(string), 10, 64) - userId, userIdErr := strconv.ParseUint(request.Context().Value("userId").(string), 10, 64) - - if productIdErr != nil || userIdErr != nil { - http.Error(writer, "Invalid listId or productId", http.StatusBadRequest) - return - } - - var requestData JsonFormatCreatePriceRequest - if err := json.NewDecoder(request.Body).Decode(&requestData); err != nil { - writer.WriteHeader(http.StatusBadRequest) - return - } - - if _, err := controller.priceRepository.Create(&model.Price{ - ProductId: productId, - UserId: userId, - Price: requestData.Price, - }); err != nil { - writer.WriteHeader(http.StatusInternalServerError) - } -} - -func (controller *DefaultController) GetPrice(writer http.ResponseWriter, request *http.Request) { - userId, err := strconv.ParseUint(request.Context().Value("userId").(string), 10, 64) - productId, err := strconv.ParseUint(request.Context().Value("productId").(string), 10, 64) - - if err != nil { - http.Error(writer, err.Error(), http.StatusBadRequest) - return - } - - value, err := controller.priceRepository.FindByIds(productId, userId) - if err != nil { - if err.Error() == ErrorPriceNotFound { - http.Error(writer, err.Error(), http.StatusNotFound) - return - } - http.Error(writer, err.Error(), http.StatusInternalServerError) - } - - writer.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(writer).Encode(value) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - } -} - -func (controller *DefaultController) PutPrice(writer http.ResponseWriter, request *http.Request) { - userId, err := strconv.ParseUint(request.Context().Value("userId").(string), 10, 64) - productId, err := strconv.ParseUint(request.Context().Value("productId").(string), 10, 64) - - if err != nil { - http.Error(writer, err.Error(), http.StatusBadRequest) - return - } - - var requestData JsonFormatUpdatePriceRequest - if err := json.NewDecoder(request.Body).Decode(&requestData); err != nil { - writer.WriteHeader(http.StatusBadRequest) - return - } - - if _, err := controller.priceRepository.Update(&model.Price{ - UserId: userId, - ProductId: productId, - Price: requestData.Price, - }); err != nil { - writer.WriteHeader(http.StatusInternalServerError) - return - } -} - -func (controller *DefaultController) DeletePrice(writer http.ResponseWriter, request *http.Request) { - userId, err := strconv.ParseUint(request.Context().Value("userId").(string), 10, 64) - productId, err := strconv.ParseUint(request.Context().Value("productId").(string), 10, 64) - - if err != nil { - http.Error(writer, err.Error(), http.StatusBadRequest) - return - } - - if err := controller.priceRepository.Delete(&model.Price{ProductId: productId, UserId: userId}); err != nil { - writer.WriteHeader(http.StatusInternalServerError) - return - } -} diff --git a/src/product-service/prices/default_controller_test.go b/src/product-service/prices/default_controller_test.go deleted file mode 100644 index 72b9c9f4..00000000 --- a/src/product-service/prices/default_controller_test.go +++ /dev/null @@ -1,539 +0,0 @@ -package prices - -import ( - "context" - "encoding/json" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices/model" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices/utils" - "net/http" - "net/http/httptest" - "reflect" - "strings" - "testing" -) - -func TestNewDefaultController(t *testing.T) { - type args struct { - priceRepository Repository - } - tests := []struct { - name string - args args - want *DefaultController - }{ - { - name: "Test construction with DemoRepository", - args: args{priceRepository: NewDemoRepository()}, - want: &DefaultController{priceRepository: NewDemoRepository()}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewDefaultController(tt.args.priceRepository); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewDefaultController() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestDefaultController_DeletePrice(t *testing.T) { - type fields struct { - priceRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - wantStatus int - }{ - { - name: "Successfully delete existing price (expect 200)", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest("DELETE", "/api/v1/price/1/1", nil) - request = request.WithContext(context.WithValue(request.Context(), "productId", "1")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "1")) - return request - }(), - }, - - wantStatus: http.StatusOK, - }, - { - name: "Bad non-numeric request (expect 400)", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: utils.CreatePriceRequestWithValues("DELETE", "/api/v1/price/abc/abc", "abc", "abc"), - }, - wantStatus: http.StatusBadRequest, - }, - { - name: "Unknown product to delete (expect 500)", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: utils.CreatePriceRequestWithValues("DELETE", "/api/v1/price/42/42", "42", "42"), - }, - wantStatus: http.StatusInternalServerError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := DefaultController{ - priceRepository: tt.fields.priceRepository, - } - controller.DeletePrice(tt.args.writer, tt.args.request) - if tt.args.writer.Code != tt.wantStatus { - t.Errorf("Expected status code %d, got %d", tt.wantStatus, tt.args.writer.Code) - } - }) - } -} - -func TestDefaultController_GetPrices(t *testing.T) { - t.Run("should return all prices", func(t *testing.T) { - controller := DefaultController{ - priceRepository: GenerateExampleDemoRepository(), - } - - writer := httptest.NewRecorder() - request := httptest.NewRequest("GET", "/api/v1/price", nil) - - // Test request - controller.GetPrices(writer, request) - - res := writer.Result() - var response []model.Price - err := json.NewDecoder(res.Body).Decode(&response) - - if err != nil { - t.Error(err) - } - - if writer.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, writer.Code) - } - - if writer.Header().Get("Content-Type") != "application/json" { - t.Errorf("Expected content type %s, got %s", - "application/json", writer.Header().Get("Content-Type")) - } - - prices := GenerateExamplePriceSlice() - - if len(response) != len(prices) { - t.Errorf("Expected count of prices is %d, got %d", - 2, len(response)) - } - }) -} - -func TestDefaultController_GetPricesByUser(t *testing.T) { - type fields struct { - priceRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - wantStatus int - }{ - { - name: "Bad non-numeric request (expect 400)", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest("GET", "/api/v1/price/user/abc", nil) - request = request.WithContext(context.WithValue(request.Context(), "userId", "abc")) - return request - }(), - }, - wantStatus: http.StatusBadRequest, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := DefaultController{ - priceRepository: tt.fields.priceRepository, - } - controller.GetPricesByUser(tt.args.writer, tt.args.request) - if tt.args.writer.Code != tt.wantStatus { - t.Errorf("Expected status code %d, got %d", tt.wantStatus, tt.args.writer.Code) - } - }) - } - - t.Run("Successfully get existing prices by user (expect 200 and prices)", func(t *testing.T) { - writer := httptest.NewRecorder() - request := httptest.NewRequest("GET", "/api/v1/price/user/1", nil) - request = request.WithContext(context.WithValue(request.Context(), "userId", "1")) - - controller := DefaultController{ - priceRepository: GenerateExampleDemoRepository(), - } - - // when - controller.GetPricesByUser(writer, request) - - // then - if writer.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, writer.Code) - } - - if writer.Header().Get("Content-Type") != "application/json" { - t.Errorf("Expected content type %s, got %s", - "application/json", writer.Header().Get("Content-Type")) - } - - result := writer.Result() - var response []model.Price - err := json.NewDecoder(result.Body).Decode(&response) - if err != nil { - t.Fatal(err.Error()) - } - - if len(response) != 1 { - t.Errorf("Expected count of prices is %d, got %d", - 1, len(response)) - } - - for i, price := range response { - if price.UserId != 1 { - t.Errorf("Expected role of user %d, got %d", 1, response[i].UserId) - } - } - }) -} - -func TestDefaultController_GetPrice(t *testing.T) { - type fields struct { - priceRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - wantStatus int - }{ - { - name: "Bad non-numeric request (expect 400)", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: utils.CreatePriceRequestWithValues("GET", "/api/v1/price/abc/abc", "abc", "abc"), - }, - wantStatus: http.StatusBadRequest, - }, - { - name: "Unknown price (expect 404)", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: utils.CreatePriceRequestWithValues("GET", "/api/v1/price/42/42", "42", "42"), - }, - wantStatus: http.StatusNotFound, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := DefaultController{ - priceRepository: tt.fields.priceRepository, - } - controller.GetPrice(tt.args.writer, tt.args.request) - if tt.args.writer.Code != tt.wantStatus { - t.Errorf("Expected status code %d, got %d", tt.wantStatus, tt.args.writer.Code) - } - }) - } - - t.Run("Successfully get existing price (expect 200 and price)", func(t *testing.T) { - writer := httptest.NewRecorder() - request := httptest.NewRequest("GET", "/api/v1/price/1/1", nil) - request = request.WithContext(context.WithValue(request.Context(), "productId", "1")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "1")) - - controller := DefaultController{ - priceRepository: GenerateExampleDemoRepository(), - } - - // when - controller.GetPrice(writer, request) - - // then - if writer.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, writer.Code) - } - - if writer.Header().Get("Content-Type") != "application/json" { - t.Errorf("Expected content type %s, got %s", - "application/json", writer.Header().Get("Content-Type")) - } - - result := writer.Result() - var response model.Price - err := json.NewDecoder(result.Body).Decode(&response) - if err != nil { - t.Fatal(err.Error()) - } - - if response.ProductId != 1 { - t.Errorf("Expected product id of price %d, got %d", 1, response.ProductId) - } - - if response.UserId != 1 { - t.Errorf("Expected user id of product %d, got %d", 1, response.UserId) - } - - if response.Price != 2.99 { - t.Errorf("Expected ean of product %f, got %f", 2.99, response.Price) - } - - }) -} - -func TestDefaultController_PostPrice(t *testing.T) { - type fields struct { - priceRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - expectedStatus int - expectedResponse string - }{ - { - name: "Valid Price", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "POST", - "/api/v1/price/4/4", - strings.NewReader(`{"price": 0.99}`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "4")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "4")) - return request - }(), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Malformed JSON", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "POST", - "/api/v1/price/5/5", - strings.NewReader(`{"price": 0.99`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "5")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "5")) - return request - }(), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - { - name: "Invalid price, incorrect Type for price (Non-numeric)", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "POST", - "/api/v1/price/5/5", - strings.NewReader(`{"price": "0.99"}`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "5")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "5")) - return request - }(), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := DefaultController{ - priceRepository: tt.fields.priceRepository, - } - controller.PostPrice(tt.args.writer, tt.args.request) - - // You can then assert the response status and content, and check against your expectations. - if tt.args.writer.Code != tt.expectedStatus { - t.Errorf("Expected status code %d, but got %d", tt.expectedStatus, tt.args.writer.Code) - } - - if tt.expectedResponse != "" { - actualResponse := tt.args.writer.Body.String() - if actualResponse != tt.expectedResponse { - t.Errorf("Expected response: %s, but got: %s", tt.expectedResponse, actualResponse) - } - } - }) - } -} - -func TestDefaultController_PutPrice(t *testing.T) { - type fields struct { - priceRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - - tests := []struct { - name string - fields fields - args args - expectedStatus int - expectedResponse string // If you want to check the response content - }{ - { - name: "Valid Update", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "PUT", - "/api/v1/price/1/1", - strings.NewReader(`{"userId": 1, "productId": 1, "price": 10.99}`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "1")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "1")) - return request - }(), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Valid Update (Partly Fields)", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "PUT", - "/api/v1/price/2/2", - strings.NewReader(`{"price": 6.50}`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "2")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "2")) - return request - }(), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Malformed JSON", - fields: fields{ - priceRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "PUT", - "/api/v1/price/2/2", - strings.NewReader(`{"price": 6.50`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "2")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "2")) - return request - }(), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - { - name: "Incorrect Type for Price (Non-numeric)", - fields: fields{ - // Set up your repository mock or test double here if needed - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "PUT", - "/api/v1/price/2/2", - strings.NewReader(`{"price": "Wrong Type"`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "2")) - request = request.WithContext(context.WithValue(request.Context(), "userId", "2")) - return request - }(), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := DefaultController{ - priceRepository: tt.fields.priceRepository, - } - controller.PutPrice(tt.args.writer, tt.args.request) - - // You can then assert the response status and content, and check against your expectations. - if tt.args.writer.Code != tt.expectedStatus { - t.Errorf("Expected status code %d, but got %d", tt.expectedStatus, tt.args.writer.Code) - } - - if tt.expectedResponse != "" { - actualResponse := tt.args.writer.Body.String() - if actualResponse != tt.expectedResponse { - t.Errorf("Expected response: %s, but got: %s", tt.expectedResponse, actualResponse) - } - } - }) - } -} diff --git a/src/product-service/prices/demo_repository.go b/src/product-service/prices/demo_repository.go deleted file mode 100644 index ae77aff8..00000000 --- a/src/product-service/prices/demo_repository.go +++ /dev/null @@ -1,122 +0,0 @@ -package prices - -import ( - "errors" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices/model" - "reflect" -) - -type priceEntryKey struct { - UserId uint64 - ProductId uint64 -} - -type DemoRepository struct { - prices map[priceEntryKey]*model.Price -} - -func NewDemoRepository() *DemoRepository { - return &DemoRepository{prices: make(map[priceEntryKey]*model.Price)} -} - -func (repo *DemoRepository) Create(price *model.Price) (*model.Price, error) { - for _, p := range repo.prices { - if reflect.DeepEqual(p, price) { - return nil, errors.New(ErrorPriceAlreadyExists) - } - } - - key := priceEntryKey{ - ProductId: price.ProductId, - UserId: price.UserId, - } - - repo.prices[key] = price - - return price, nil -} - -func (repo *DemoRepository) FindAll() ([]*model.Price, error) { - if repo.prices == nil { - return nil, errors.New(ErrorPriceList) - } - - prices := make([]*model.Price, 0, len(repo.prices)) - for _, price := range repo.prices { - prices = append(prices, price) - } - - return prices, nil -} - -func (repo *DemoRepository) FindAllByUser(userId uint64) ([]*model.Price, error) { - if repo.prices == nil { - return nil, errors.New(ErrorPriceList) - } - - var userPrices []*model.Price - for _, price := range repo.prices { - if price.UserId == userId { - userPrices = append(userPrices, price) - } - } - - return userPrices, nil -} - -func (repo *DemoRepository) FindAllByProduct(productId uint64) ([]*model.Price, error) { - if repo.prices == nil { - return nil, errors.New(ErrorPriceList) - } - - var productPrices []*model.Price - for _, price := range repo.prices { - if price.ProductId == productId { - productPrices = append(productPrices, price) - } - } - - return productPrices, nil -} - -func (repo *DemoRepository) FindByIds(productId uint64, userId uint64) (*model.Price, error) { - key := priceEntryKey{ - UserId: userId, - ProductId: productId, - } - - price, exists := repo.prices[key] - - if !exists { - return nil, errors.New(ErrorPriceNotFound) - } - - return price, nil -} - -func (repo *DemoRepository) Update(price *model.Price) (*model.Price, error) { - existingPrice, foundError := repo.FindByIds(price.ProductId, price.UserId) - - if foundError != nil { - return nil, errors.New(ErrorPriceUpdate) - } - - existingPrice.Price = price.Price - - return existingPrice, nil -} - -func (repo *DemoRepository) Delete(price *model.Price) error { - key := priceEntryKey{ - UserId: price.UserId, - ProductId: price.ProductId, - } - - _, exists := repo.prices[key] - if !exists { - return errors.New(ErrorPriceDeletion) - } - - delete(repo.prices, key) - return nil -} diff --git a/src/product-service/prices/demo_repository_example.go b/src/product-service/prices/demo_repository_example.go deleted file mode 100644 index 23514ea7..00000000 --- a/src/product-service/prices/demo_repository_example.go +++ /dev/null @@ -1,28 +0,0 @@ -package prices - -import "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices/model" - -func GenerateExampleDemoRepository() Repository { - repository := NewDemoRepository() - pricesSlice := GenerateExamplePriceSlice() - for _, price := range pricesSlice { - repository.Create(price) - } - - return repository -} - -func GenerateExamplePriceSlice() []*model.Price { - return []*model.Price{ - { - UserId: 1, - ProductId: 1, - Price: 2.99, - }, - { - UserId: 2, - ProductId: 2, - Price: 5.99, - }, - } -} diff --git a/src/product-service/prices/demo_repository_test.go b/src/product-service/prices/demo_repository_test.go deleted file mode 100644 index de59088f..00000000 --- a/src/product-service/prices/demo_repository_test.go +++ /dev/null @@ -1,280 +0,0 @@ -package prices - -import ( - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/prices/model" - "reflect" - "testing" -) - -func TestNewDemoRepository(t *testing.T) { - t.Run("Demo repository correct initialized", func(t *testing.T) { - got := NewDemoRepository() - if len(got.prices) != 0 { - t.Errorf("NewDemoRepository().prices has unexpected length, want 0") - } - }) -} - -func TestDemoRepository_Create(t *testing.T) { - demoRepository := NewDemoRepository() - - price := model.Price{ - UserId: 1, - ProductId: 1, - Price: 2.99, - } - - t.Run("Create price with success", func(t *testing.T) { - _, err := demoRepository.Create(&price) - if err != nil { - t.Error(err) - } - }) - - t.Run("Check for doublet", func(t *testing.T) { - _, err := demoRepository.Create(&price) - if err.Error() != ErrorPriceAlreadyExists { - t.Error(err) - } - }) -} - -func TestDemoRepository_FindAll(t *testing.T) { - demoRepository := NewDemoRepository() - - prices := []*model.Price{ - { - UserId: 1, - ProductId: 1, - Price: 2.99, - }, - { - UserId: 2, - ProductId: 3, - Price: 0.55, - }, - } - - for _, price := range prices { - _, err := demoRepository.Create(price) - if err != nil { - t.Error("Failed to add prepared price for test") - } - } - - t.Run("Fetch all prices", func(t *testing.T) { - fetchedPrices, err := demoRepository.FindAll() - if err != nil { - t.Error("Can't fetch prices") - } - - if len(fetchedPrices) != len(prices) { - t.Errorf("Unexpected price count. Expected %d, got %d", len(prices), len(fetchedPrices)) - } - }) - - priceTest := []struct { - name string - want *model.Price - }{ - { - name: "first", - want: prices[0], - }, - { - name: "second", - want: prices[1], - }, - } - - for _, tt := range priceTest { - t.Run("Is fetched price matching with "+tt.name+" added price?", func(t *testing.T) { - fetchedPrices, _ := demoRepository.FindAll() - found := false - - for _, fetchedPrice := range fetchedPrices { - if reflect.DeepEqual(tt.want, fetchedPrice) { - found = true - break - } - } - - if !found { - t.Error("Fetched price does not match original price") - } - }) - } -} - -func TestDemoRepository_FindAllByUser(t *testing.T) { - demoRepository := NewDemoRepository() - - prices := []*model.Price{ - { - UserId: 1, - ProductId: 1, - Price: 2.99, - }, - { - UserId: 2, - ProductId: 3, - Price: 0.55, - }, - } - - for _, price := range prices { - _, err := demoRepository.Create(price) - if err != nil { - t.Fatalf("Failed to add prepared price for test: %v", err) - } - } - - t.Run("Fetch prices for a specific user", func(t *testing.T) { - fetchedPrices, err := demoRepository.FindAllByUser(2) - if err != nil { - t.Fatalf("Error fetching prices: %v", err) - } - - if len(fetchedPrices) != 1 { - t.Errorf("Unexpected price count. Expected 1, got %d", len(fetchedPrices)) - } - - if !reflect.DeepEqual(prices[1], fetchedPrices[0]) { - t.Error("Fetched price does not match the expected price") - } - }) - - t.Run("Verify fetched prices match added prices", func(t *testing.T) { - fetchedPrices, err := demoRepository.FindAll() - if err != nil { - t.Fatalf("Error fetching prices: %v", err) - } - - for _, tt := range prices { - found := false - - for _, fetchedPrice := range fetchedPrices { - if reflect.DeepEqual(tt, fetchedPrice) { - found = true - break - } - } - - if !found { - t.Errorf("Fetched price for user id %d does not match the original price", tt.UserId) - } - } - }) -} - -func TestDemoRepository_FindByIds(t *testing.T) { - demoRepository := NewDemoRepository() - - price := model.Price{ - UserId: 1, - ProductId: 1, - Price: 2.99, - } - - _, err := demoRepository.Create(&price) - if err != nil { - t.Fatal("Failed to add prepare price for test") - } - - t.Run("Fetch price with existing ids", func(t *testing.T) { - _, err := demoRepository.FindByIds(price.ProductId, price.UserId) - if err != nil { - t.Errorf("Can't find expected price with product id %d and user id %d", price.ProductId, price.UserId) - } - - t.Run("Is fetched price matching with added price?", func(t *testing.T) { - fetchedPrice, _ := demoRepository.FindByIds(price.ProductId, price.UserId) - if !reflect.DeepEqual(price, *fetchedPrice) { - t.Error("Fetched price does not match original price") - } - }) - }) - - t.Run("Non-existing price test", func(t *testing.T) { - _, err = demoRepository.FindByIds(42, 42) - if err.Error() != ErrorPriceNotFound { - t.Error(err) - } - }) -} - -func TestDemoRepository_Update(t *testing.T) { - demoRepository := NewDemoRepository() - - price := model.Price{ - UserId: 1, - ProductId: 1, - Price: 2.99, - } - - fetchedPrice, err := demoRepository.Create(&price) - if err != nil { - t.Error("Failed to add prepare price for test") - } - - t.Run("Check if updated price object has updated price", func(t *testing.T) { - price := model.Price{ - UserId: 1, - ProductId: 1, - Price: 3.99, - } - - updatedPrice, err := demoRepository.Update(&price) - if err != nil { - t.Error(err.Error()) - } - - if fetchedPrice.Price != updatedPrice.Price { - t.Errorf("Failed to update price. Got %f, want %f.", - fetchedPrice.Price, updatedPrice.Price) - } - }) -} - -func TestDemoRepository_Delete(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - price := model.Price{ - UserId: 1, - ProductId: 1, - Price: 2.99, - } - - fetchedPrice, err := demoRepository.Create(&price) - if err != nil { - t.Error("Failed to add prepare price for test") - } - - t.Run("Test for deletion", func(t *testing.T) { - err = demoRepository.Delete(fetchedPrice) - if err != nil { - t.Errorf("Failed to delete price with product id %d and user id %d", price.ProductId, price.UserId) - } - - t.Run("Try to fetch deleted price", func(t *testing.T) { - fetchedPrice, err = demoRepository.FindByIds(price.ProductId, price.UserId) - if err.Error() != ErrorPriceNotFound { - t.Errorf("Price with with product id %d and user id %d was not deleted", price.ProductId, price.UserId) - } - }) - }) - - t.Run("Try to delete non-existing price", func(t *testing.T) { - fakePrice := model.Price{ - UserId: 2, - ProductId: 2, - Price: 5.99, - } - - err = demoRepository.Delete(&fakePrice) - if err.Error() != ErrorPriceDeletion { - t.Errorf("Price with product id %d and user id %d was deleted", price.ProductId, price.UserId) - } - }) -} diff --git a/src/product-service/products/default_controller.go b/src/product-service/products/default_controller.go deleted file mode 100644 index 23ca8913..00000000 --- a/src/product-service/products/default_controller.go +++ /dev/null @@ -1,138 +0,0 @@ -package products - -import ( - "encoding/json" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/products/model" - "net/http" - "strconv" -) - -type defaultController struct { - productRepository Repository -} - -func NewDefaultController(productRepository Repository) *defaultController { - return &defaultController{productRepository} -} - -func (controller *defaultController) GetProducts(writer http.ResponseWriter, request *http.Request) { - values, err := controller.productRepository.FindAll() - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - } - - writer.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(writer).Encode(values) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - return - } -} - -func (controller *defaultController) PostProduct(writer http.ResponseWriter, request *http.Request) { - var requestData JsonFormatCreateProductRequest - if err := json.NewDecoder(request.Body).Decode(&requestData); err != nil { - writer.WriteHeader(http.StatusBadRequest) - return - } - - product, err := controller.productRepository.Create(&model.Product{ - Description: requestData.Description, - Ean: requestData.Ean, - }) - - if err != nil { - writer.WriteHeader(http.StatusInternalServerError) - return - } - - writer.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(writer).Encode(product) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - return - } -} - -func (controller *defaultController) GetProductById(writer http.ResponseWriter, request *http.Request) { - productId, err := strconv.ParseUint(request.Context().Value("productId").(string), 10, 64) - if err != nil { - http.Error(writer, err.Error(), http.StatusBadRequest) - return - } - - value, err := controller.productRepository.FindById(productId) - if err != nil { - if err.Error() == ErrorProductNotFound { - http.Error(writer, err.Error(), http.StatusNotFound) - return - } - http.Error(writer, err.Error(), http.StatusInternalServerError) - } - - writer.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(writer).Encode(value) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - } -} - -func (controller *defaultController) GetProductByEan(writer http.ResponseWriter, request *http.Request) { - productEan, exists := request.Context().Value("productEan").(string) - if !exists { - writer.WriteHeader(http.StatusBadRequest) - return - } - - values, err := controller.productRepository.FindByEan(productEan) - if err != nil { - if err.Error() == ErrorProductNotFound { - http.Error(writer, err.Error(), http.StatusNotFound) - return - } - http.Error(writer, err.Error(), http.StatusInternalServerError) - } - - writer.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(writer).Encode(values) - if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) - return - } -} - -func (controller *defaultController) PutProduct(writer http.ResponseWriter, request *http.Request) { - productId, err := strconv.ParseUint(request.Context().Value("productId").(string), 10, 64) - if err != nil { - http.Error(writer, err.Error(), http.StatusBadRequest) - return - } - - var requestData JsonFormatUpdateProductRequest - if err := json.NewDecoder(request.Body).Decode(&requestData); err != nil { - writer.WriteHeader(http.StatusBadRequest) - return - } - - if _, err := controller.productRepository.Update(&model.Product{ - Id: productId, - Description: requestData.Description, - Ean: requestData.Ean, - }); err != nil { - writer.WriteHeader(http.StatusInternalServerError) - return - } -} - -func (controller *defaultController) DeleteProduct(writer http.ResponseWriter, request *http.Request) { - productId, err := strconv.ParseUint(request.Context().Value("productId").(string), 10, 64) - if err != nil { - http.Error(writer, err.Error(), http.StatusBadRequest) - return - } - - if err := controller.productRepository.Delete(&model.Product{Id: productId}); err != nil { - writer.WriteHeader(http.StatusInternalServerError) - return - } -} diff --git a/src/product-service/products/default_controller_test.go b/src/product-service/products/default_controller_test.go deleted file mode 100644 index 4ce5f1a9..00000000 --- a/src/product-service/products/default_controller_test.go +++ /dev/null @@ -1,520 +0,0 @@ -package products - -import ( - "context" - "encoding/json" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/products/model" - "net/http" - "net/http/httptest" - "reflect" - "sort" - "strings" - "testing" -) - -func TestNewDefaultController(t *testing.T) { - type args struct { - productRepository Repository - } - tests := []struct { - name string - args args - want *defaultController - }{ - { - name: "Test construction with DemoRepository", - args: args{productRepository: NewDemoRepository()}, - want: &defaultController{productRepository: NewDemoRepository()}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewDefaultController(tt.args.productRepository); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewDefaultController() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestDefaultController_DeleteProduct(t *testing.T) { - type fields struct { - productRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - wantStatus int - }{ - { - name: "Successfully delete existing product (expect 200)", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest("DELETE", "/api/v1/product/1", nil) - request = request.WithContext(context.WithValue(request.Context(), "productId", "1")) - return request - }(), - }, - wantStatus: http.StatusOK, - }, - { - name: "Bad non-numeric request (expect 400)", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest("DELETE", "/api/v1/product/abc", nil) - request = request.WithContext(context.WithValue(request.Context(), "productId", "abc")) - return request - }(), - }, - wantStatus: http.StatusBadRequest, - }, - { - name: "Unknown product to delete (expect 500)", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest("DELETE", "/api/v1/product/5", nil) - request = request.WithContext(context.WithValue(request.Context(), "productId", "5")) - return request - }(), - }, - wantStatus: http.StatusInternalServerError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := defaultController{ - productRepository: tt.fields.productRepository, - } - controller.DeleteProduct(tt.args.writer, tt.args.request) - if tt.args.writer.Code != tt.wantStatus { - t.Errorf("Expected status code %d, got %d", tt.wantStatus, tt.args.writer.Code) - } - }) - } -} - -func TestDefaultController_GetProductById(t *testing.T) { - type fields struct { - productRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - wantStatus int - }{ - { - name: "Bad non-numeric request (expect 400)", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest("GET", "/api/v1/product/abc", nil) - request = request.WithContext(context.WithValue(request.Context(), "productId", "abc")) - return request - }(), - }, - wantStatus: http.StatusBadRequest, - }, - { - name: "Unknown product (expect 404)", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest("GET", "/api/v1/product/4", nil) - request = request.WithContext(context.WithValue(request.Context(), "productId", "4")) - return request - }(), - }, - wantStatus: http.StatusNotFound, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := defaultController{ - productRepository: tt.fields.productRepository, - } - controller.GetProductById(tt.args.writer, tt.args.request) - if tt.args.writer.Code != tt.wantStatus { - t.Errorf("Expected status code %d, got %d", tt.wantStatus, tt.args.writer.Code) - } - }) - } - - t.Run("Successfully get existing product (expect 200 and product)", func(t *testing.T) { - writer := httptest.NewRecorder() - request := httptest.NewRequest("GET", "/api/v1/product/1", nil) - request = request.WithContext(context.WithValue(request.Context(), "productId", "1")) - - controller := defaultController{ - productRepository: GenerateExampleDemoRepository(), - } - - // when - controller.GetProductById(writer, request) - - // then - if writer.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, writer.Code) - } - - if writer.Header().Get("Content-Type") != "application/json" { - t.Errorf("Expected content type %s, got %s", - "application/json", writer.Header().Get("Content-Type")) - } - - result := writer.Result() - var response model.Product - err := json.NewDecoder(result.Body).Decode(&response) - if err != nil { - t.Fatal(err.Error()) - } - - if response.Id != 1 { - t.Errorf("Expected id of product %d, got %d", 1, response.Id) - } - - if response.Description != "Strauchtomaten" { - t.Errorf("Expected description of product %s, got %s", "Strauchtomaten", response.Description) - } - - if response.Ean != "4014819040771" { - t.Errorf("Expected ean of product %s, got %s", "4014819040771", response.Ean) - } - - }) -} - -func TestDefaultController_GetProductByEan(t *testing.T) { - t.Run("Unknown product (expect 404)", func(t *testing.T) { - controller := defaultController{ - productRepository: GenerateExampleDemoRepository(), - } - - writer := httptest.NewRecorder() - request := httptest.NewRequest("GET", "/api/v1/products/ean?ean=123", nil) - request = request.WithContext(context.WithValue(request.Context(), "productEan", "123")) - - // Test request - controller.GetProductByEan(writer, request) - - if writer.Code != http.StatusNotFound { - t.Errorf("Expected status code %d, got %d", http.StatusNotFound, writer.Code) - } - }) - - t.Run("Should return products by EAN", func(t *testing.T) { - controller := defaultController{ - productRepository: GenerateExampleDemoRepository(), - } - - writer := httptest.NewRecorder() - request := httptest.NewRequest("GET", "/api/v1/products/ean?ean=4014819040771", nil) - request = request.WithContext(context.WithValue(request.Context(), "productEan", "4014819040771")) - - // Test request - controller.GetProductByEan(writer, request) - - if writer.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, writer.Code) - } - - res := writer.Result() - var response model.Product - err := json.NewDecoder(res.Body).Decode(&response) - - if err != nil { - t.Error(err) - } - - // Add assertions based on your expected response. - // For example, check the length, content, etc. - // ... - - }) -} - -func TestDefaultController_GetProducts(t *testing.T) { - t.Run("should return all products", func(t *testing.T) { - controller := defaultController{ - productRepository: GenerateExampleDemoRepository(), - } - - writer := httptest.NewRecorder() - request := httptest.NewRequest("GET", "/api/v1/product", nil) - - // Test request - controller.GetProducts(writer, request) - - res := writer.Result() - var response []model.Product - err := json.NewDecoder(res.Body).Decode(&response) - - if err != nil { - t.Error(err) - } - - if writer.Code != http.StatusOK { - t.Errorf("Expected status code %d, got %d", http.StatusOK, writer.Code) - } - - if writer.Header().Get("Content-Type") != "application/json" { - t.Errorf("Expected content type %s, got %s", - "application/json", writer.Header().Get("Content-Type")) - } - - products := GenerateExampleProductSlice() - - sort.Slice(response, func(i, j int) bool { - return response[i].Id < response[j].Id - }) - - if len(response) != len(products) { - t.Errorf("Expected count of product is %d, got %d", - 2, len(response)) - } - - for i, product := range products { - if product.Id != response[i].Id { - t.Errorf("Expected id of product %d, got %d", product.Id, response[i].Id) - } - - if product.Description != response[i].Description { - t.Errorf("Expected description of product %s, got %s", product.Description, response[i].Description) - } - - if product.Ean != response[i].Ean { - t.Errorf("Expected ean of product %s, got %s", product.Ean, response[i].Ean) - } - } - - }) -} - -func TestDefaultController_PostProduct(t *testing.T) { - type fields struct { - productRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - expectedStatus int - expectedResponse string - }{ - { - name: "Valid Product", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/product", - strings.NewReader(`{"id": 3, "description": "Test Product", "ean": "12345"}`), - ), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Valid Product (Partly Fields)", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/product", - strings.NewReader(`{"description": "Incomplete Product"}`), - ), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Malformed JSON", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/product", - strings.NewReader(`{"description": "Incomplete Product"`), - ), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := defaultController{ - productRepository: tt.fields.productRepository, - } - controller.PostProduct(tt.args.writer, tt.args.request) - - // You can then assert the response status and content, and check against your expectations. - if tt.args.writer.Code != tt.expectedStatus { - t.Errorf("Expected status code %d, but got %d", tt.expectedStatus, tt.args.writer.Code) - } - - if tt.expectedResponse != "" { - actualResponse := tt.args.writer.Body.String() - if actualResponse != tt.expectedResponse { - t.Errorf("Expected response: %s, but got: %s", tt.expectedResponse, actualResponse) - } - } - }) - } -} - -func TestDefaultController_PutProduct(t *testing.T) { - type fields struct { - productRepository Repository - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - - tests := []struct { - name string - fields fields - args args - expectedStatus int - expectedResponse string // If you want to check the response content - }{ - { - name: "Valid Update", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "PUT", - "/api/v1/product/1", - strings.NewReader(`{"id": 1, "description": "Updated Product", "ean": "54321"}`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "1")) - return request - }(), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Valid Update (Partly Fields)", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "PUT", - "/api/v1/product/2", - strings.NewReader(`{"description": "Incomplete Update"}`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "2")) - return request - }(), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Malformed JSON", - fields: fields{ - productRepository: GenerateExampleDemoRepository(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "PUT", - "/api/v1/product/2", - strings.NewReader(`{"description": "Incomplete Update"`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "2")) - return request - }(), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - { - name: "Incorrect Type for EAN (Non-numeric)", - fields: fields{ - // Set up your repository mock or test double here if needed - }, - args: args{ - writer: httptest.NewRecorder(), - request: func() *http.Request { - var request = httptest.NewRequest( - "PUT", - "/api/v1/product/2", - strings.NewReader(`{"ean": "Wrong Type"`)) - request = request.WithContext(context.WithValue(request.Context(), "productId", "2")) - return request - }(), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - controller := defaultController{ - productRepository: tt.fields.productRepository, - } - controller.PutProduct(tt.args.writer, tt.args.request) - - // You can then assert the response status and content, and check against your expectations. - if tt.args.writer.Code != tt.expectedStatus { - t.Errorf("Expected status code %d, but got %d", tt.expectedStatus, tt.args.writer.Code) - } - - if tt.expectedResponse != "" { - actualResponse := tt.args.writer.Body.String() - if actualResponse != tt.expectedResponse { - t.Errorf("Expected response: %s, but got: %s", tt.expectedResponse, actualResponse) - } - } - }) - } -} diff --git a/src/product-service/products/demo_repository.go b/src/product-service/products/demo_repository.go deleted file mode 100644 index eb42d34f..00000000 --- a/src/product-service/products/demo_repository.go +++ /dev/null @@ -1,101 +0,0 @@ -package products - -import ( - "errors" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/products/model" - "sort" -) - -type DemoRepository struct { - products map[uint64]*model.Product -} - -func NewDemoRepository() *DemoRepository { - return &DemoRepository{products: make(map[uint64]*model.Product)} -} - -func (repo *DemoRepository) Create(product *model.Product) (*model.Product, error) { - if product.Id == 0 { - product.Id = repo.findNextAvailableID() - } - - if foundProductWithEan, _ := repo.FindByEan(product.Ean); foundProductWithEan != nil { - return nil, errors.New(ErrorEanAlreadyExists) - } - - if _, found := repo.products[product.Id]; found { - return nil, errors.New(ErrorProductAlreadyExists) - } - - repo.products[product.Id] = product - - return product, nil -} - -func (repo *DemoRepository) FindAll() ([]*model.Product, error) { - if repo.products != nil { - r := make([]*model.Product, 0, len(repo.products)) - for _, v := range repo.products { - r = append(r, v) - } - - sort.Slice(r, func(i, j int) bool { - return r[i].Description < r[j].Description - }) - return r, nil - } - - return nil, errors.New(ErrorProductsList) -} - -func (repo *DemoRepository) FindById(id uint64) (*model.Product, error) { - product, found := repo.products[id] - if found { - return product, nil - } - - return nil, errors.New(ErrorProductNotFound) -} - -func (repo *DemoRepository) FindByEan(ean string) (*model.Product, error) { - for _, entry := range repo.products { - if entry.Ean == ean { - return entry, nil - } - } - - return nil, errors.New(ErrorProductNotFound) -} - -func (repo *DemoRepository) Update(product *model.Product) (*model.Product, error) { - existingProduct, foundError := repo.FindById(product.Id) - - if foundError != nil { - return nil, errors.New(ErrorProductUpdate) - } - - existingProduct.Description = product.Description - existingProduct.Ean = product.Ean - - return existingProduct, nil -} - -func (repo *DemoRepository) Delete(product *model.Product) error { - _, found := repo.products[product.Id] - if found { - delete(repo.products, product.Id) - return nil - } - - return errors.New(ErrorProductDeletion) -} - -func (repo *DemoRepository) findNextAvailableID() uint64 { - var maxID uint64 - for id := range repo.products { - if id > maxID { - maxID = id - } - } - return maxID + 1 -} diff --git a/src/product-service/products/demo_repository_example.go b/src/product-service/products/demo_repository_example.go deleted file mode 100644 index 82acebf3..00000000 --- a/src/product-service/products/demo_repository_example.go +++ /dev/null @@ -1,28 +0,0 @@ -package products - -import "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/products/model" - -func GenerateExampleDemoRepository() Repository { - repository := NewDemoRepository() - productSlice := GenerateExampleProductSlice() - for _, product := range productSlice { - repository.Create(product) - } - - return repository -} - -func GenerateExampleProductSlice() []*model.Product { - return []*model.Product{ - { - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - }, - { - Id: 2, - Description: "Lauchzwiebeln", - Ean: "5001819040871", - }, - } -} diff --git a/src/product-service/products/demo_repository_test.go b/src/product-service/products/demo_repository_test.go deleted file mode 100644 index 9fda78a3..00000000 --- a/src/product-service/products/demo_repository_test.go +++ /dev/null @@ -1,326 +0,0 @@ -package products - -import ( - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/product-service/products/model" - "reflect" - "sort" - "testing" -) - -func TestNewDemoRepository(t *testing.T) { - t.Run("Demo repository correct initialized", func(t *testing.T) { - want := make(map[uint64]*model.Product) - if got := NewDemoRepository(); !reflect.DeepEqual(got.products, want) { - t.Errorf("NewDemoRepository().products = %v, want %v", got.products, want) - } - }) -} - -func TestDemoRepository_Create(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - product := model.Product{ - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - } - - productWithDuplicateEan := model.Product{ - Id: 2, - Description: "Dinkelnudeln", - Ean: "4014819040771", - } - - t.Run("Create product with success", func(t *testing.T) { - _, err := demoRepository.Create(&product) - if err != nil { - t.Error(err) - } - }) - - t.Run("Check if products with duplicate ean can not be created", func(t *testing.T) { - _, err := demoRepository.Create(&productWithDuplicateEan) - if err.Error() != ErrorEanAlreadyExists { - t.Error(err) - } - }) - - t.Run("Check for doublet", func(t *testing.T) { - _, err := demoRepository.Create(&product) - if err.Error() != ErrorProductAlreadyExists && err.Error() != ErrorEanAlreadyExists { - t.Error(err) - } - }) -} - -func TestDemoRepository_FindAll(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - products := []*model.Product{ - { - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - }, - { - Id: 2, - Description: "Lauchzwiebeln", - Ean: "5001819040871", - }, - } - - for _, product := range products { - _, err := demoRepository.Create(product) - if err != nil { - t.Error("Failed to add prepared product for test") - } - } - - t.Run("Fetch all products", func(t *testing.T) { - fetchedProducts, err := demoRepository.FindAll() - if err != nil { - t.Error("Can't fetch products") - } - - if len(fetchedProducts) != len(products) { - t.Errorf("Unexpected product count. Expected %d, got %d", len(products), len(fetchedProducts)) - } - }) - - productTests := []struct { - name string - want *model.Product - }{ - { - name: "first", - want: products[0], - }, - { - name: "second", - want: products[1], - }, - } - - for i, tt := range productTests { - t.Run("Is fetched product matching with "+tt.name+" added product?", func(t *testing.T) { - fetchedProducts, _ := demoRepository.FindAll() - sort.Slice(fetchedProducts, func(i, j int) bool { - return fetchedProducts[i].Id < fetchedProducts[j].Id - }) - if !reflect.DeepEqual(tt.want, fetchedProducts[i]) { - t.Error("Fetched product does not match original product") - } - }) - } -} - -func TestDemoRepository_FindById(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - product := model.Product{ - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - } - - _, err := demoRepository.Create(&product) - if err != nil { - t.Fatal("Failed to add prepare product for test") - } - - t.Run("Fetch product with existing id", func(t *testing.T) { - _, err := demoRepository.FindById(product.Id) - if err != nil { - t.Errorf("Can't find expected product with id %d", product.Id) - } - - t.Run("Is fetched product matching with added product?", func(t *testing.T) { - fetchedProduct, _ := demoRepository.FindById(product.Id) - if !reflect.DeepEqual(product, *fetchedProduct) { - t.Error("Fetched product does not match original product") - } - }) - }) - - t.Run("Non-existing product test", func(t *testing.T) { - _, err = demoRepository.FindById(42) - if err.Error() != ErrorProductNotFound { - t.Error(err) - } - }) -} - -func TestDemoRepository_FindByEan(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - product := model.Product{ - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - } - - _, err := demoRepository.Create(&product) - if err != nil { - t.Fatal("Failed to add prepare product for test") - } - - t.Run("Fetch product with existing ean", func(t *testing.T) { - _, err := demoRepository.FindByEan(product.Ean) - if err != nil { - t.Errorf("Can't find expected product with ean %s", product.Ean) - } - - t.Run("Is fetched product matching with added product?", func(t *testing.T) { - fetchedProduct, _ := demoRepository.FindByEan(product.Ean) - if !reflect.DeepEqual(product, *fetchedProduct) { - t.Error("Fetched product does not match original product") - } - }) - }) - - t.Run("Non-existing product test", func(t *testing.T) { - _, err = demoRepository.FindByEan("42") - if err.Error() != ErrorProductNotFound { - t.Error(err) - } - }) -} - -func TestDemoRepository_Update(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - product := model.Product{ - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - } - - fetchedProduct, err := demoRepository.Create(&product) - if err != nil { - t.Error("Failed to add prepare product for test") - } - - t.Run("Check if updated product has updated description", func(t *testing.T) { - updateProduct := model.Product{ - Id: 1, - Description: "Wittenseer Mineralwasser", - Ean: "4014819040771", - } - updatedProduct, err := demoRepository.Update(&updateProduct) - if err != nil { - t.Error(err.Error()) - } - - if fetchedProduct.Description != updatedProduct.Description { - t.Errorf("Failed to update product description. Got %s, want %s.", - fetchedProduct.Description, updateProduct.Description) - } - }) -} - -func TestDemoRepository_Delete(t *testing.T) { - // Prepare test - productsRepository := NewDemoRepository() - - product := model.Product{ - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - } - - fetchedProduct, err := productsRepository.Create(&product) - if err != nil { - t.Error("Failed to add prepare product for test") - } - - t.Run("Test for deletion", func(t *testing.T) { - err = productsRepository.Delete(fetchedProduct) - if err != nil { - t.Errorf("Failed to delete product with id %d", product.Id) - } - - t.Run("Try to fetch deleted product", func(t *testing.T) { - fetchedProduct, err = productsRepository.FindById(product.Id) - if err.Error() != ErrorProductNotFound { - t.Errorf("Product with id %d was not deleted", product.Id) - } - }) - }) - - t.Run("Try to delete non-existing product", func(t *testing.T) { - fakeProduct := model.Product{ - Id: 1, - Description: "Lauchzwiebeln", - Ean: "5001819040871", - } - - err = productsRepository.Delete(&fakeProduct) - if err.Error() != ErrorProductDeletion { - t.Errorf("Product with id %d was deleted", product.Id) - } - }) -} - -func TestDemoRepository_findNextAvailableID(t *testing.T) { - type fields struct { - products map[uint64]*model.Product - } - tests := []struct { - name string - fields fields - want uint64 - }{ - { - name: "Check if next available id is correct", - fields: fields{products: map[uint64]*model.Product{ - 1: { - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - }, - 2: { - Id: 2, - Description: "Lauchzwiebeln", - Ean: "5001819040871", - }, - }}, - want: 3, - }, - { - name: "Check if next available id is correct with gaps in map", - fields: fields{products: map[uint64]*model.Product{ - 1: { - Id: 1, - Description: "Strauchtomaten", - Ean: "4014819040771", - }, - 3: { - Id: 3, - Description: "Lauchzwiebeln", - Ean: "5001819040871", - }, - }}, - want: 4, - }, - { - name: "check if next available id is correct with empty map", - fields: fields{products: make(map[uint64]*model.Product)}, - want: 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - repo := &DemoRepository{ - products: tt.fields.products, - } - if got := repo.findNextAvailableID(); got != tt.want { - t.Errorf("findNextAvailableID() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/src/shoppinglist-service/api/router/router.go b/src/shoppinglist-service/api/router/router.go index 1d8c0c98..c5b484ba 100644 --- a/src/shoppinglist-service/api/router/router.go +++ b/src/shoppinglist-service/api/router/router.go @@ -18,17 +18,19 @@ func New( ) *Router { r := router.New() - r.GET("/api/v1/shoppinglist/:userId", (*shoppingListController).GetLists, authMiddleware) - r.GET("/api/v1/shoppinglist/:listId/:userId", (*shoppingListController).GetList, authMiddleware) - r.PUT("/api/v1/shoppinglist/:listId/:userId", (*shoppingListController).PutList, authMiddleware) - r.POST("/api/v1/shoppinglist/:userId", (*shoppingListController).PostList, authMiddleware) - r.DELETE("/api/v1/shoppinglist/:listId", (*shoppingListController).DeleteList, authMiddleware) - - r.GET("/api/v1/shoppinglistentries/:listId", (*shoppingListEntryController).GetEntries, authMiddleware) - r.GET("/api/v1/shoppinglistentries/:listId/:productId", (*shoppingListEntryController).GetEntry, authMiddleware) - r.PUT("/api/v1/shoppinglistentries/:listId/:productId", (*shoppingListEntryController).PutEntry, authMiddleware) - r.POST("/api/v1/shoppinglistentries/:listId/:productId", (*shoppingListEntryController).PostEntry, authMiddleware) - r.DELETE("/api/v1/shoppinglistentries/:listId/:productId", (*shoppingListEntryController).DeleteEntry, authMiddleware) + r.RegisterMiddleware(authMiddleware) + + r.GET("/api/v1/shoppinglist/:userId", (*shoppingListController).GetLists) + r.GET("/api/v1/shoppinglist/:listId/:userId", (*shoppingListController).GetList) + r.PUT("/api/v1/shoppinglist/:listId/:userId", (*shoppingListController).PutList) + r.POST("/api/v1/shoppinglist/:userId", (*shoppingListController).PostList) + r.DELETE("/api/v1/shoppinglist/:listId", (*shoppingListController).DeleteList) + + r.GET("/api/v1/shoppinglistentries/:listId", (*shoppingListEntryController).GetEntries) + r.GET("/api/v1/shoppinglistentries/:listId/:productId", (*shoppingListEntryController).GetEntry) + r.PUT("/api/v1/shoppinglistentries/:listId/:productId", (*shoppingListEntryController).PutEntry) + r.POST("/api/v1/shoppinglistentries/:listId/:productId", (*shoppingListEntryController).PostEntry) + r.DELETE("/api/v1/shoppinglistentries/:listId/:productId", (*shoppingListEntryController).DeleteEntry) return &Router{r} } diff --git a/src/shoppinglist-service/userShoppingList/demo_repository.go b/src/shoppinglist-service/userShoppingList/demo_repository.go deleted file mode 100644 index 629e0ae8..00000000 --- a/src/shoppinglist-service/userShoppingList/demo_repository.go +++ /dev/null @@ -1,105 +0,0 @@ -package userShoppingList - -import ( - "errors" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/shoppinglist-service/userShoppingList/model" - "sort" -) - -type DemoRepository struct { - shoppingLists map[uint64]*model.UserShoppingList -} - -func NewDemoRepository() *DemoRepository { - return &DemoRepository{shoppingLists: make(map[uint64]*model.UserShoppingList)} -} - -func (repo *DemoRepository) Create(shoppingList *model.UserShoppingList) (*model.UserShoppingList, error) { - var listId uint64 - if shoppingList.Id == 0 { - listId = repo.findNextAvailableID() - shoppingList.Id = listId - } else { - listId = shoppingList.Id - } - - _, found := repo.shoppingLists[listId] - if found { - return nil, errors.New(ErrorListAlreadyExists) - } - repo.shoppingLists[listId] = shoppingList - - return shoppingList, nil -} - -func (repo *DemoRepository) Update(shoppingList *model.UserShoppingList) (*model.UserShoppingList, error) { - existingShoppingList, foundError := repo.FindById(shoppingList.Id) - - if foundError != nil { - return nil, errors.New(ErrorListUpdate) - } - - existingShoppingList.Description = shoppingList.Description - existingShoppingList.Completed = shoppingList.Completed - - return existingShoppingList, nil -} - -func (repo *DemoRepository) FindAllById(userId uint64) ([]*model.UserShoppingList, error) { - lists := []*model.UserShoppingList{} - - for _, shoppingList := range repo.shoppingLists { - if shoppingList.UserId == userId { - lists = append(lists, shoppingList) - } - } - - if len(lists) == 0 { - return nil, errors.New(ErrorListNotFound) - } - - sort.Slice(lists, func(i, j int) bool { - return lists[i].Description < lists[j].Description - }) - - return lists, nil -} - -func (repo *DemoRepository) FindById(Id uint64) (*model.UserShoppingList, error) { - shoppingList, found := repo.shoppingLists[Id] - if found { - return shoppingList, nil - } - - return nil, errors.New(ErrorListNotFound) -} - -func (repo *DemoRepository) FindByIds(userId uint64, listId uint64) (*model.UserShoppingList, error) { - for _, shoppingList := range repo.shoppingLists { - if shoppingList.UserId == userId && shoppingList.Id == listId { - return shoppingList, nil - } - } - - return nil, errors.New(ErrorListNotFound) -} - -func (repo *DemoRepository) Delete(shoppingList *model.UserShoppingList) error { - _, found := repo.shoppingLists[shoppingList.Id] - if found { - delete(repo.shoppingLists, shoppingList.Id) - return nil - } - - return errors.New(ErrorListDeletion) -} - -func (repo *DemoRepository) findNextAvailableID() uint64 { - var maxID uint64 - for id := range repo.shoppingLists { - if id > maxID { - maxID = id - } - } - return maxID + 1 -} diff --git a/src/shoppinglist-service/userShoppingList/demo_repository_test.go b/src/shoppinglist-service/userShoppingList/demo_repository_test.go deleted file mode 100644 index 65171276..00000000 --- a/src/shoppinglist-service/userShoppingList/demo_repository_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package userShoppingList - -import ( - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/shoppinglist-service/userShoppingList/model" - "testing" -) - -func TestNewDemoRepository(t *testing.T) { - t.Run("Demo repository correctly initialized", func(t *testing.T) { - repo := NewDemoRepository() - if repo == nil { - t.Error("NewDemoRepository returned nil") - } - }) -} - -func TestDemoRepository_Create(t *testing.T) { - repo := NewDemoRepository() - - list := &model.UserShoppingList{ - Id: 1, - UserId: 1, - Description: "New Shoppinglist", - Completed: false, - } - - t.Run("Create shopping list with success", func(t *testing.T) { - createdList, err := repo.Create(list) - if err != nil { - t.Error(err) - } - - if createdList.Id != list.Id { - t.Errorf("Expected created shopping list to have ID %d, but got %d", list.Id, createdList.Id) - } - - if createdList.Description != list.Description { - t.Errorf("Expected created shopping list to have description %s, but got %s", list.Description, createdList.Description) - } - }) - - t.Run("Attempt to create a duplicate shopping list", func(t *testing.T) { - _, err := repo.Create(list) - if err == nil { - t.Error("Expected an error for duplicate shopping list creation") - } - }) -} - -func TestDemoRepository_FindAllById(t *testing.T) { - repo := NewDemoRepository() - - lists := []*model.UserShoppingList{ - { - Id: 1, - UserId: 1, - Completed: false, - }, - { - Id: 2, - UserId: 2, - Completed: false, - }, - { - Id: 3, - UserId: 1, - Completed: true, - }, - } - - for _, list := range lists { - _, _ = repo.Create(list) - } - - t.Run("Find all lists for a user with existing lists", func(t *testing.T) { - userLists, err := repo.FindAllById(1) - if err != nil { - t.Error(err) - } - - if len(userLists) != 2 { - t.Errorf("Expected 2 lists for user 1, but got %d", len(userLists)) - } - }) - - t.Run("Find all lists for a user with no existing lists", func(t *testing.T) { - _, err := repo.FindAllById(42) - if err == nil { - t.Error("Expected an error for user with no lists") - } - }) -} - -func TestDemoRepository_FindByIds(t *testing.T) { - repo := NewDemoRepository() - - lists := []*model.UserShoppingList{ - { - Id: 1, - UserId: 1, - Completed: false, - }, - { - Id: 2, - UserId: 2, - Completed: false, - }, - } - - for _, list := range lists { - _, _ = repo.Create(list) - } - - t.Run("Find list by IDs with existing list", func(t *testing.T) { - foundList, err := repo.FindByIds(1, 1) - if err != nil { - t.Error(err) - } - - if foundList.Id != 1 { - t.Errorf("Expected list with ID 1, but got list with ID %d", foundList.Id) - } - }) - - t.Run("Find list by IDs with non-existing list", func(t *testing.T) { - _, err := repo.FindByIds(1, 42) - if err == nil { - t.Error("Expected an error for non-existing list") - } - }) -} - -func TestDemoRepository_FindById(t *testing.T) { - repo := NewDemoRepository() - - lists := []*model.UserShoppingList{ - { - Id: 1, - UserId: 1, - Completed: false, - }, - { - Id: 2, - UserId: 2, - Completed: false, - }, - } - - for _, list := range lists { - _, _ = repo.Create(list) - } - - t.Run("Find list by ID with existing list", func(t *testing.T) { - foundList, err := repo.FindById(1) - if err != nil { - t.Error(err) - } - - if foundList.Id != 1 { - t.Errorf("Expected list with ID 1, but got list with ID %d", foundList.Id) - } - }) - - t.Run("Find list by ID with non-existing list", func(t *testing.T) { - _, err := repo.FindById(42) - if err == nil { - t.Error("Expected an error for non-existing list") - } - }) -} - -func TestDemoRepository_Update(t *testing.T) { - repo := NewDemoRepository() - - list := &model.UserShoppingList{ - Id: 1, - UserId: 1, - Description: "Update list description", - Completed: true, - } - - _, _ = repo.Create(list) - - t.Run("Update an existing shopping list", func(t *testing.T) { - updatedList, err := repo.Update(list) - if err != nil { - t.Error(err) - } - - if updatedList.Description != list.Description { - t.Errorf("Expected updated shopping list to have description %s, but got %s", - list.Description, updatedList.Description) - } - - if updatedList.Completed != list.Completed { - t.Errorf("Expected updated shopping list to have completed value %t, but got %t", - list.Completed, updatedList.Completed) - } - }) - - t.Run("Attempt to update a non-existing shopping list", func(t *testing.T) { - fakeList := &model.UserShoppingList{ - Id: 42, - UserId: 2, - Completed: true, - } - - _, err := repo.Update(fakeList) - if err == nil { - t.Error("Expected an error for updating a non-existing shopping list") - } - }) -} - -func TestDemoRepository_Delete(t *testing.T) { - repo := NewDemoRepository() - - list := &model.UserShoppingList{ - Id: 1, - UserId: 1, - Completed: false, - } - - _, _ = repo.Create(list) - - t.Run("Delete an existing shopping list", func(t *testing.T) { - err := repo.Delete(list) - if err != nil { - t.Error(err) - } - - // Ensure the list is deleted - _, err = repo.FindById(list.Id) - if err == nil { - t.Error("Expected the shopping list to be deleted") - } - }) - - t.Run("Attempt to delete a non-existing shopping list", func(t *testing.T) { - fakeList := &model.UserShoppingList{ - Id: 42, - UserId: 2, - Completed: false, - } - - err := repo.Delete(fakeList) - if err == nil { - t.Error("Expected an error for deleting a non-existing shopping list") - } - }) -} - -func TestDemoRepository_findNextAvailableID(t *testing.T) { - repo := NewDemoRepository() - - lists := []*model.UserShoppingList{ - { - Id: 1, - UserId: 1, - Completed: false, - }, - { - Id: 2, - UserId: 2, - Completed: false, - }, - } - - for _, list := range lists { - _, _ = repo.Create(list) - } - - t.Run("Next available ID when there are no gaps", func(t *testing.T) { - nextID := repo.findNextAvailableID() - if nextID != 3 { - t.Errorf("Expected next available ID to be 4, but got %d", nextID) - } - }) - - t.Run("Next available ID with gaps in IDs", func(t *testing.T) { - delete(repo.shoppingLists, 2) - nextID := repo.findNextAvailableID() - if nextID != 2 { - t.Errorf("Expected next available ID to be 2, but got %d", nextID) - } - }) -} diff --git a/src/shoppinglist-service/userShoppingListEntry/demo_repository.go b/src/shoppinglist-service/userShoppingListEntry/demo_repository.go deleted file mode 100644 index 95c75080..00000000 --- a/src/shoppinglist-service/userShoppingListEntry/demo_repository.go +++ /dev/null @@ -1,94 +0,0 @@ -package userShoppingListEntry - -import ( - "errors" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/shoppinglist-service/userShoppingListEntry/model" -) - -type shoppingListEntryKey struct { - ShoppingListID uint64 - ProductID uint64 -} - -type DemoRepository struct { - entries map[shoppingListEntryKey]*model.UserShoppingListEntry -} - -func NewDemoRepository() *DemoRepository { - return &DemoRepository{entries: make(map[shoppingListEntryKey]*model.UserShoppingListEntry)} -} - -func (repo *DemoRepository) Create(entry *model.UserShoppingListEntry) (*model.UserShoppingListEntry, error) { - _, err := repo.FindByIds(entry.ShoppingListId, entry.ProductId) - if err == nil { - return nil, errors.New(ErrorEntryAlreadyExists) - } - key := shoppingListEntryKey{ - ShoppingListID: entry.ShoppingListId, - ProductID: entry.ProductId, - } - - repo.entries[key] = entry - - return entry, nil -} - -func (repo *DemoRepository) Delete(entry *model.UserShoppingListEntry) error { - key := shoppingListEntryKey{ - ShoppingListID: entry.ShoppingListId, - ProductID: entry.ProductId, - } - - _, exists := repo.entries[key] - if !exists { - return errors.New(ErrorEntryDeletion) - } - - delete(repo.entries, key) - return nil -} - -func (repo *DemoRepository) Update(entry *model.UserShoppingListEntry) (*model.UserShoppingListEntry, error) { - existingEntry, foundError := repo.FindByIds(entry.ShoppingListId, entry.ProductId) - - if foundError != nil { - return nil, errors.New(ErrorEntryUpdate) - } - - existingEntry.Count = entry.Count - existingEntry.Note = entry.Note - existingEntry.Checked = entry.Checked - - return existingEntry, nil -} - -func (repo *DemoRepository) FindByIds(shoppingListId uint64, productId uint64) (*model.UserShoppingListEntry, error) { - key := shoppingListEntryKey{ - ShoppingListID: shoppingListId, - ProductID: productId, - } - - entry, exists := repo.entries[key] - - if !exists { - return nil, errors.New(ErrorEntryNotFound) - } - - return entry, nil -} - -func (repo *DemoRepository) FindAll(shoppingListId uint64) ([]*model.UserShoppingListEntry, error) { - entries := []*model.UserShoppingListEntry{} - - for key, entry := range repo.entries { - if key.ShoppingListID == shoppingListId { - entries = append(entries, entry) - } - } - - if len(entries) == 0 { - return nil, errors.New(ErrorEntryNotFound) - } - - return entries, nil -} diff --git a/src/shoppinglist-service/userShoppingListEntry/demo_repository_test.go b/src/shoppinglist-service/userShoppingListEntry/demo_repository_test.go deleted file mode 100644 index b4a844af..00000000 --- a/src/shoppinglist-service/userShoppingListEntry/demo_repository_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package userShoppingListEntry - -import ( - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/shoppinglist-service/userShoppingListEntry/model" - "testing" -) - -func TestNewDemoRepository(t *testing.T) { - t.Run("Demo repository correctly initialized", func(t *testing.T) { - repo := NewDemoRepository() - if repo == nil { - t.Error("NewDemoRepository returned nil") - } - }) -} - -func TestDemoRepository_Create(t *testing.T) { - repo := NewDemoRepository() - - entry := &model.UserShoppingListEntry{ - ShoppingListId: 1, - ProductId: 1, - Count: 2, - Note: "This is very important", - Checked: false, - } - - t.Run("Create entry with success", func(t *testing.T) { - createdEntry, err := repo.Create(entry) - if err != nil { - t.Error(err) - } - - if createdEntry.ShoppingListId != entry.ShoppingListId || createdEntry.ProductId != entry.ProductId { - t.Errorf("Expected created entry to have ShoppingListId %d and ProductId %d, but got ShoppingListId %d and ProductId %d", - entry.ShoppingListId, entry.ProductId, createdEntry.ShoppingListId, createdEntry.ProductId) - } - }) - - t.Run("Attempt to create a duplicate entry", func(t *testing.T) { - _, err := repo.Create(entry) - if err == nil { - t.Error("Expected an error for duplicate entry creation") - } - }) -} - -func TestDemoRepository_FindAll(t *testing.T) { - repo := NewDemoRepository() - - entries := []*model.UserShoppingListEntry{ - { - ShoppingListId: 1, - ProductId: 1, - Count: 2, - Note: "This is very important", - Checked: false, - }, - { - ShoppingListId: 1, - ProductId: 2, - Count: 5, - Note: "I really want this", - Checked: false, - }, - { - ShoppingListId: 2, - ProductId: 1, - Count: 9, - Note: "Please get this", - Checked: false, - }, - } - - for _, entry := range entries { - _, _ = repo.Create(entry) - } - - t.Run("Find all entries for a shopping list with existing entries", func(t *testing.T) { - shoppingListId := uint64(1) - foundEntries, err := repo.FindAll(shoppingListId) - if err != nil { - t.Error(err) - } - - if len(foundEntries) != 2 { - t.Errorf("Expected 2 entries for shopping list %d, but got %d", shoppingListId, len(foundEntries)) - } - }) - - t.Run("Find all entries for a shopping list with no existing entries", func(t *testing.T) { - shoppingListId := uint64(3) - _, err := repo.FindAll(shoppingListId) - if err == nil { - t.Error("Expected an error for shopping list with no entries") - } - }) -} - -func TestDemoRepository_FindByIds(t *testing.T) { - repo := NewDemoRepository() - - entry := &model.UserShoppingListEntry{ - ShoppingListId: 1, - ProductId: 1, - Count: 2, - Note: "This is very important", - Checked: false, - } - - _, _ = repo.Create(entry) - - t.Run("Find entry by IDs with existing entry", func(t *testing.T) { - foundEntry, err := repo.FindByIds(entry.ShoppingListId, entry.ProductId) - if err != nil { - t.Error(err) - } - - if foundEntry.ShoppingListId != entry.ShoppingListId || foundEntry.ProductId != entry.ProductId { - t.Errorf("Expected entry with ShoppingListId %d and ProductId %d, but got ShoppingListId %d and ProductId %d", - entry.ShoppingListId, entry.ProductId, foundEntry.ShoppingListId, foundEntry.ProductId) - } - }) - - t.Run("Find entry by IDs with non-existing entry", func(t *testing.T) { - nonExistentEntry, err := repo.FindByIds(2, 2) - if err == nil { - t.Error("Expected an error for non-existing entry") - } - - if nonExistentEntry != nil { - t.Errorf("Expected non-existing entry to be nil, but got an entry") - } - }) -} - -func TestDemoRepository_Update(t *testing.T) { - repo := NewDemoRepository() - - entry := &model.UserShoppingListEntry{ - ShoppingListId: 1, - ProductId: 1, - Count: 3, - Note: "I really want this", - Checked: true, - } - - _, _ = repo.Create(entry) - - t.Run("Update an existing entry", func(t *testing.T) { - updatedEntry, err := repo.Update(entry) - if err != nil { - t.Error(err) - } - - if updatedEntry.Count != entry.Count { - t.Errorf("Expected updated shopping list entry to have count %d, but got count %d", - entry.Count, updatedEntry.Count) - } - - if updatedEntry.Note != entry.Note { - t.Errorf("Expected updated shopping list entry to have note %s, but got note %s", - entry.Note, updatedEntry.Note) - } - - if updatedEntry.Checked != entry.Checked { - t.Errorf("Expected updated shopping list entry to have checked value %t, but got note %t", - entry.Checked, updatedEntry.Checked) - } - }) - - t.Run("Attempt to update a non-existing entry", func(t *testing.T) { - fakeEntry := &model.UserShoppingListEntry{ - ShoppingListId: 2, - ProductId: 2, - Count: 2, - Note: "This is very important", - Checked: false, - } - - _, err := repo.Update(fakeEntry) - if err == nil { - t.Error("Expected an error for updating a non-existing entry") - } - }) -} - -func TestDemoRepository_Delete(t *testing.T) { - repo := NewDemoRepository() - - entry := &model.UserShoppingListEntry{ - ShoppingListId: 1, - ProductId: 1, - Count: 1, - Note: "This is very important to me", - Checked: false, - } - - _, _ = repo.Create(entry) - - t.Run("Delete an existing entry", func(t *testing.T) { - err := repo.Delete(entry) - if err != nil { - t.Error(err) - } - - // Ensure the entry is deleted - _, err = repo.FindByIds(entry.ShoppingListId, entry.ProductId) - if err == nil { - t.Error("Expected the entry to be deleted") - } - }) - - t.Run("Attempt to delete a non-existing entry", func(t *testing.T) { - fakeEntry := &model.UserShoppingListEntry{ - ShoppingListId: 2, - ProductId: 2, - Count: 2, - Note: "This is very important", - Checked: false, - } - - err := repo.Delete(fakeEntry) - if err == nil { - t.Error("Expected an error for deleting a non-existing entry") - } - }) -} diff --git a/src/user-service/api/http/handler/login_test.go b/src/user-service/api/http/handler/login_test.go index 02c89638..92a5252d 100644 --- a/src/user-service/api/http/handler/login_test.go +++ b/src/user-service/api/http/handler/login_test.go @@ -1,12 +1,14 @@ package handler import ( - "encoding/json" - "github.com/stretchr/testify/assert" + "errors" + "github.com/stretchr/testify/mock" "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/auth" - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/auth/utils" + authMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/auth/_mock" "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/crypto" + cryptoMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/crypto/_mock" "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user" + userMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/_mock" "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/model" "net/http" "net/http/httptest" @@ -15,213 +17,157 @@ import ( ) func TestLoginHandler(t *testing.T) { - type fields struct { - loginHandler *LoginHandler - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - expectedStatus int - expectedResponse string - }{ - { - name: "Valid User (expect 200)", - fields: fields{ - loginHandler: setupLoginHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/login", - strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "123456"}`), - ), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Invalid User Mail (expect 500)", - fields: fields{ - loginHandler: setupLoginHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authenticationy/login", - strings.NewReader(`{"email": "adaa.lovelace@gmail.com", "password": "123456"}`), - ), - }, - expectedStatus: http.StatusInternalServerError, - expectedResponse: "", - }, - { - name: "Invalid Request - Empty Password (expect 400)", - fields: fields{ - loginHandler: setupLoginHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/login", - strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": ""}`), - ), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - { - name: "Wrong password (expect 401)", - fields: fields{ - loginHandler: setupLoginHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/login", - strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "98765"}`), - ), - }, - expectedStatus: http.StatusUnauthorized, - expectedResponse: "", - }, - { - name: "Malformed JSON (expect 400)", - fields: fields{ - loginHandler: setupLoginHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/login", - strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "123456"`), - ), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - { - name: "Missing field (expect 400)", - fields: fields{ - loginHandler: setupLoginHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/login", - strings.NewReader(`{"email": "ada.lovelace@gmail.com"`), - ), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - { - name: "Invalid user data, incorrect Type for Email and Password (expected String) (expect 400)", - fields: fields{ - loginHandler: setupLoginHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/login", - strings.NewReader(`{"email": 120, "password": 120`), - ), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - tt.fields.loginHandler.Login(tt.args.writer, tt.args.request) - - // You can then assert the response status and content, and check against your expectations. - if tt.args.writer.Code != tt.expectedStatus { - t.Errorf("Expected status code %d, but got %d", tt.expectedStatus, tt.args.writer.Code) - } - - if tt.expectedResponse != "" { - actualResponse := tt.args.writer.Body.String() - if actualResponse != tt.expectedResponse { - t.Errorf("Expected response: %s, but got: %s", tt.expectedResponse, actualResponse) - } - } - }) - } - - loginHandler := setupLoginHandler() - - writer := httptest.NewRecorder() - request := httptest.NewRequest( - "POST", - "/api/v1/authentication/login", - strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "123456"}`), - ) - - loginHandler.Login(writer, request) - - res := writer.Result() - var response map[string]interface{} - err := json.NewDecoder(res.Body).Decode(&response) - - assert.NoError(t, err) - assert.Contains(t, response["access_token"], "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9") - assert.Equal(t, "Bearer", response["token_type"]) - assert.Equal(t, float64(3600), response["expires_in"]) - assert.Equal(t, http.StatusOK, writer.Code) -} + mockUsersRepository := userMock.MockRepository{} + var userRepository user.Repository = &mockUsersRepository + mockHasher := cryptoMock.NewMockHasher(t) + var hasher crypto.Hasher = mockHasher + mockTokenGenerator := authMock.MockTokenGenerator{} + var tokenGenerator auth.TokenGenerator = &mockTokenGenerator -func setupLoginHandler() *LoginHandler { - pemPrivateKey := utils.GenerateRandomECDSAPrivateKeyAsPEM() + loginHandler := NewLoginHandler(userRepository, hasher, tokenGenerator) - var jwtToken, _ = auth.NewJwtTokenGenerator( - auth.JwtConfig{PrivateKey: pemPrivateKey}) + t.Run("Valid User (expect 200)", func(t *testing.T) { + expectedStatus := http.StatusOK - return NewLoginHandler(setupMockRepository(), - crypto.NewBcryptHasher(), jwtToken) -} + mockUsersRepository.EXPECT().FindByEmail("ada.lovelace@gmail.com").Return(&model.User{ + Id: 1, + Email: "ada.lovelace@gmail.com", + Password: []byte("123456"), + Name: "Ada Lovelace", + Role: model.Customer, + }, nil).Once() -func setupMockRepository() user.Repository { - repository := user.NewDemoRepository() - userSlice := setupDemoUserSlice() - for _, newUser := range userSlice { - _, _ = repository.Create(newUser) - } + mockTokenGenerator.EXPECT().CreateToken(mock.Anything).Return("valid-token", nil).Once() - return repository -} + mockHasher.EXPECT().Validate([]byte("123456"), []byte("123456")).Return(true).Once() + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/login", + strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "123456"}`), + ) + + loginHandler.Login(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("Invalid User Mail (expect 500)", func(t *testing.T) { + expectedStatus := http.StatusInternalServerError + + mockUsersRepository.EXPECT().FindByEmail("ada.lovelace@gmail.com").Return(nil, errors.New(user.ErrorUserNotFound)).Once() + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/login", + strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "123456"}`), + ) + + loginHandler.Login(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("Invalid Request - Empty Password (expect 400)", func(t *testing.T) { + expectedStatus := http.StatusBadRequest + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/login", + strings.NewReader(`{"email": "ada.lovelace@gmail.com"}`), + ) + + loginHandler.Login(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) -func setupDemoUserSlice() []*model.User { - bcryptHasher := crypto.NewBcryptHasher() - hashedPassword, _ := bcryptHasher.Hash([]byte("123456")) + t.Run("Wrong password (expect 401)", func(t *testing.T) { + expectedStatus := http.StatusUnauthorized - return []*model.User{ - { + mockUsersRepository.EXPECT().FindByEmail("ada.lovelace@gmail.com").Return(&model.User{ Id: 1, Email: "ada.lovelace@gmail.com", - Password: hashedPassword, + Password: []byte("894544"), Name: "Ada Lovelace", Role: model.Customer, - }, - { - Id: 2, - Email: "alan.turin@gmail.com", - Password: hashedPassword, - Name: "Alan Turing", - Role: model.Customer, - }, - } + }, nil) + + mockTokenGenerator.EXPECT().CreateToken(mock.Anything).Return("valid-token", nil).Once() + + mockHasher.EXPECT().Validate([]byte("123456"), []byte("894544")).Return(false).Once() + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/login", + strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "123456"}`), + ) + + loginHandler.Login(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("Malformed JSON (expect 400)", func(t *testing.T) { + expectedStatus := http.StatusBadRequest + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/login", + strings.NewReader(`{"email: "ada.lovelace@gmail.com`), + ) + + loginHandler.Login(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("Missing field (expect 400)", func(t *testing.T) { + expectedStatus := http.StatusBadRequest + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/login", + strings.NewReader(`{"password": "12345"}`), + ) + + loginHandler.Login(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("Invalid user data, incorrect Type for Email and Password (expected String) (expect 400)", func(t *testing.T) { + expectedStatus := http.StatusBadRequest + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/login", + strings.NewReader(`{"email": 120 "password": 120}`), + ) + + loginHandler.Login(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) } diff --git a/src/user-service/api/http/handler/register_test.go b/src/user-service/api/http/handler/register_test.go index a363cbc1..95d631cf 100644 --- a/src/user-service/api/http/handler/register_test.go +++ b/src/user-service/api/http/handler/register_test.go @@ -1,7 +1,12 @@ package handler import ( + "errors" "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/crypto" + cryptoMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/crypto/_mock" + "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user" + userMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/_mock" + "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/model" "net/http" "net/http/httptest" "strings" @@ -9,139 +14,156 @@ import ( ) func TestRegisterHandler(t *testing.T) { - type fields struct { - registerHandler *RegisterHandler - } - type args struct { - writer *httptest.ResponseRecorder - request *http.Request - } - tests := []struct { - name string - fields fields - args args - expectedStatus int - expectedResponse string - }{ - { - name: "Valid User (expect 200)", - fields: fields{ - registerHandler: setUpRegisterHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/register", - strings.NewReader(`{"email": "grace.hopper@gmail.com", "password": "123456", "name": "Grace Hopper", "role": 0}`), - ), - }, - expectedStatus: http.StatusOK, - expectedResponse: "", - }, - { - name: "Invalid Request - Empty Password (expect 400)", - fields: fields{ - registerHandler: setUpRegisterHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/register", - strings.NewReader(`{"email": "grace.hopper2@gmail.com", "password": "", "name": "Grace Hopper", "role": 0}`), - ), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - { - name: "User already exists (expect 409)", - fields: fields{ - registerHandler: setUpRegisterHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/register", - strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "123456", "name": "Ada Lovelace", "role": 0}`), - ), - }, - expectedStatus: http.StatusConflict, - expectedResponse: "", - }, - { - name: "User should not be able to register as admin (expect 403)", - fields: fields{ - registerHandler: setUpRegisterHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/register", - strings.NewReader(`{"email": "ada.lovelace@gmail.com", "password": "123456", "name": "Ada Lovelace", "role": 2}`), - ), - }, - expectedStatus: http.StatusForbidden, - expectedResponse: "", - }, - { - name: "Malformed JSON (expect 400)", - fields: fields{ - registerHandler: setUpRegisterHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/register", - strings.NewReader(`{"email": "grace.hopper@gmail.com", "password": "123456", "name": "Grace Hopper", "role": 0`), - ), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - { - name: "Invalid user data, incorrect Type for Email and Password (expected String) (expect 400)", - fields: fields{ - registerHandler: setUpRegisterHandler(), - }, - args: args{ - writer: httptest.NewRecorder(), - request: httptest.NewRequest( - "POST", - "/api/v1/authentication/register", - strings.NewReader(`{"email": "grace.hopper@gmail.com", "password": 123456, "name": "Grace Hopper", "role": false}`), - ), - }, - expectedStatus: http.StatusBadRequest, - expectedResponse: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - tt.fields.registerHandler.Register(tt.args.writer, tt.args.request) - - // You can then assert the response status and content, and check against your expectations. - if tt.args.writer.Code != tt.expectedStatus { - t.Errorf("Expected status code %d, but got %d", tt.expectedStatus, tt.args.writer.Code) - } - - if tt.expectedResponse != "" { - actualResponse := tt.args.writer.Body.String() - if actualResponse != tt.expectedResponse { - t.Errorf("Expected response: %s, but got: %s", tt.expectedResponse, actualResponse) - } - } - }) - } -} + mockUsersRepository := userMock.MockRepository{} + var userRepository user.Repository = &mockUsersRepository + mockHasher := cryptoMock.NewMockHasher(t) + var hasher crypto.Hasher = mockHasher + + registerHandler := NewRegisterHandler(userRepository, hasher) + + t.Run("Valid User (expect 200)", func(t *testing.T) { + expectedStatus := http.StatusOK + expectedResponse := `{"id":0,"name":"Grace Hopper","email":"grace.hopper@gmail.com","role":0}` + + userToCreate := &model.User{ + Email: "grace.hopper@gmail.com", + Password: []byte("123456"), + Name: "Grace Hopper", + Role: 0, + } + + mockUsersRepository.EXPECT().FindByEmail("grace.hopper@gmail.com").Return(nil, errors.New(user.ErrorUserNotFound)).Once() + mockHasher.EXPECT().Hash([]byte("123456")).Return([]byte("123456"), nil).Once() + mockUsersRepository.EXPECT().Create(userToCreate).Return(userToCreate, nil).Once() + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/register", + strings.NewReader(`{"email": "grace.hopper@gmail.com", "password": "123456", "name": "Grace Hopper", "role": 0}`), + ) + + registerHandler.Register(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + + if strings.Compare(expectedResponse, writer.Body.String()) == 0 { + t.Errorf("Expected response %s, but got %s", expectedResponse, writer.Body.String()) + } + + }) + + t.Run("Invalid Request - Empty Password (expect 400)", func(t *testing.T) { + expectedStatus := http.StatusBadRequest + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/register", + strings.NewReader(`{"email": "grace.hopper@gmail.com", "name": "Grace Hopper", "role": 0}`), + ) + + registerHandler.Register(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("User already exists (expect 409)", func(t *testing.T) { + expectedStatus := http.StatusConflict + + userToCreate := &model.User{ + Email: "grace.hopper@gmail.com", + Password: []byte("123456"), + Name: "Grace Hopper", + Role: 0, + } + + mockUsersRepository.EXPECT().FindByEmail("grace.hopper@gmail.com").Return(userToCreate, nil).Once() + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/register", + strings.NewReader(`{"email": "grace.hopper@gmail.com", "password": "123456", "name": "Grace Hopper", "role": 0}`), + ) + + registerHandler.Register(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("User should not be able to register as admin (expect 403)", func(t *testing.T) { + expectedStatus := http.StatusForbidden + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/register", + strings.NewReader(`{"email": "grace.hopper@gmail.com", "password": "123456", "name": "Grace Hopper", "role": 2}`), + ) + + registerHandler.Register(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + + }) + + t.Run("Invalid Request - Empty Password (expect 400)", func(t *testing.T) { + expectedStatus := http.StatusBadRequest + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/register", + strings.NewReader(`{"email": "grace.hopper@gmail.com", "password": "", "name": "Grace Hopper", "role": 0}`), + ) + + registerHandler.Register(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("Malformed JSON (expect 400)", func(t *testing.T) { + expectedStatus := http.StatusBadRequest + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/register", + strings.NewReader(`{"eil: "grace.hom", ame": "Grace Hopper", "role: 0`), + ) + + registerHandler.Register(writer, request) + + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) + + t.Run("Invalid user data, incorrect Type for Email and Password (expected String) (expect 400)", func(t *testing.T) { + expectedStatus := http.StatusBadRequest + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/api/v1/authentication/register", + strings.NewReader(`{"email": 120, "password": 120, "name": "Grace Hopper", "role": 0}`), + ) + + registerHandler.Register(writer, request) -func setUpRegisterHandler() *RegisterHandler { - return NewRegisterHandler(setupMockRepository(), - crypto.NewBcryptHasher()) + if writer.Code != expectedStatus { + t.Errorf("Expected status code %d, but got %d", expectedStatus, writer.Code) + } + }) } diff --git a/src/user-service/api/http/middleware/auth_test.go b/src/user-service/api/http/middleware/auth_test.go new file mode 100644 index 00000000..e02f460a --- /dev/null +++ b/src/user-service/api/http/middleware/auth_test.go @@ -0,0 +1,148 @@ +package middleware + +import ( + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/auth" + authMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/auth/_mock" + "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user" + userMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/_mock" + "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/model" + "net/http/httptest" + "testing" +) + +func TestCreateLocalAuthMiddleware(t *testing.T) { + mockUserRepository := userMock.NewMockRepository(t) + var userRepository user.Repository = mockUserRepository + mockTokenGenerator := authMock.NewMockTokenGenerator(t) + var tokenGenerator auth.TokenGenerator = mockTokenGenerator + + validToken := "valid-token" + invalidToken := "invalid-token" + + expectedUser := &model.User{ + Id: 1, + Email: "ada.lovelace@gmail.com", + Name: "Ada Lovelace", + Role: 0, + } + + middleware := CreateLocalAuthMiddleware(&userRepository, tokenGenerator) + + t.Run("ValidToken", func(t *testing.T) { + mockUserRepository.EXPECT().FindById(expectedUser.Id).Return(expectedUser, nil).Once() + + claims := map[string]interface{}{"id": float64(expectedUser.Id), "email": expectedUser.Email, "name": expectedUser.Name, "role": float64(expectedUser.Role)} + mockTokenGenerator.EXPECT().VerifyToken(mock.Anything).Return(claims, nil).Once() + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "GET", + "/restricted/url", nil) + request.Header.Set("Authorization", "Bearer "+validToken) + + response := middleware(writer, request) + + assert.NotNil(t, response) + assert.Equal(t, expectedUser.Id, response.Context().Value("auth_userId")) + assert.Equal(t, expectedUser.Email, response.Context().Value("auth_userEmail")) + assert.Equal(t, expectedUser.Name, response.Context().Value("auth_userName")) + assert.Equal(t, expectedUser.Role, response.Context().Value("auth_userRole")) + }) + + t.Run("InvalidToken", func(t *testing.T) { + mockTokenGenerator.EXPECT().VerifyToken(mock.Anything).Return(nil, errors.New("invalid token")).Once() + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "GET", + "/restricted/url", nil) + request.Header.Set("Authorization", "Bearer "+invalidToken) + + response := middleware(writer, request) + + assert.NotNil(t, response) + assert.Nil(t, response.Context().Value("auth_userId")) + assert.Nil(t, response.Context().Value("auth_userEmail")) + assert.Nil(t, response.Context().Value("auth_userName")) + assert.Nil(t, response.Context().Value("auth_userRole")) + }) + + t.Run("InvalidClaims", func(t *testing.T) { + claims := map[string]interface{}{} + mockTokenGenerator.EXPECT().VerifyToken(mock.Anything).Return(claims, nil).Once() + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "GET", + "/restricted/url", nil) + request.Header.Set("Authorization", "Bearer "+validToken) + response := middleware(writer, request) + + assert.NotNil(t, response) + assert.Nil(t, response.Context().Value("auth_userId")) + assert.Nil(t, response.Context().Value("auth_userEmail")) + assert.Nil(t, response.Context().Value("auth_userName")) + assert.Nil(t, response.Context().Value("auth_userRole")) + }) + + t.Run("NonExistentUser", func(t *testing.T) { + nonExistingUserId := uint64(999) + claims := map[string]interface{}{"id": float64(nonExistingUserId)} + mockTokenGenerator.EXPECT().VerifyToken(mock.Anything).Return(claims, nil).Once() + mockUserRepository.EXPECT().FindById(nonExistingUserId).Return(nil, errors.New(user.ErrorUserNotFound)).Once() + + writer := httptest.NewRecorder() + request := httptest.NewRequest( + "GET", + "/restricted/url", nil) + request.Header.Set("Authorization", "Bearer "+validToken) + response := middleware(writer, request) + + assert.NotNil(t, response) + assert.Nil(t, response.Context().Value("auth_userId")) + assert.Nil(t, response.Context().Value("auth_userEmail")) + assert.Nil(t, response.Context().Value("auth_userName")) + assert.Nil(t, response.Context().Value("auth_userRole")) + }) +} + +func Test_getToken(t *testing.T) { + tests := []struct { + name string + authHeader string + expectedToken string + expectError bool + }{ + {"No Authorization Header", "", "", true}, + {"Invalid Format", "InvalidToken", "", true}, + {"Only Bearer", "Bearer", "", true}, + {"Valid Token", "Bearer mytesttoken", "mytesttoken", false}, + {"Extra Parts", "Bearer mytesttoken extra", "", true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + req := httptest.NewRequest("GET", "http://example.com", nil) + if tc.authHeader != "" { + req.Header.Add("Authorization", tc.authHeader) + } + + token, err := getToken(req) + + if tc.expectError { + if err == nil { + t.Errorf("Expected an error but didn't get one") + } + } else { + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if token != tc.expectedToken { + t.Errorf("Expected token %v, got %v", tc.expectedToken, token) + } + } + }) + } +} diff --git a/src/user-service/api/rpc/server_test.go b/src/user-service/api/rpc/server_test.go new file mode 100644 index 00000000..94a4b027 --- /dev/null +++ b/src/user-service/api/rpc/server_test.go @@ -0,0 +1,81 @@ +package rpc + +import ( + "context" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + proto "hsfl.de/group6/hsfl-master-ai-cloud-engineering/lib/rpc/user" + "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/auth" + authMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/auth/_mock" + "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user" + userMock "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/_mock" + "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/model" + "testing" +) + +func TestNewUserServiceServer(t *testing.T) { + mockUsersRepository := userMock.NewMockRepository(t) + var userRepository user.Repository = mockUsersRepository + mockTokenGenerator := authMock.NewMockTokenGenerator(t) + var tokenGenerator auth.TokenGenerator = mockTokenGenerator + + server := NewUserServiceServer(&userRepository, tokenGenerator) + + assert.NotNil(t, server) + assert.NotNil(t, server.userRepository, userRepository) + assert.NotNil(t, server.tokenGenerator, tokenGenerator) +} + +func TestUserServiceServer_ValidateUserToken(t *testing.T) { + mockUserRepository := userMock.NewMockRepository(t) + var userRepository user.Repository = mockUserRepository + mockTokenGenerator := authMock.NewMockTokenGenerator(t) + var tokenGenerator auth.TokenGenerator = mockTokenGenerator + + server := NewUserServiceServer(&userRepository, tokenGenerator) + + t.Run("ValidToken", func(t *testing.T) { + expectedUserId := uint64(1) + expectedUser := &model.User{Id: expectedUserId, Email: "test@example.com", Name: "Test User", Role: 1} + mockUserRepository.EXPECT().FindById(expectedUserId).Return(expectedUser, nil).Once() + + claims := map[string]interface{}{"id": float64(expectedUserId)} + mockTokenGenerator.EXPECT().VerifyToken(mock.Anything).Return(claims, nil).Once() + + response, err := server.ValidateUserToken(context.Background(), &proto.ValidateUserTokenRequest{Token: "valid-token"}) + + assert.NoError(t, err) + assert.NotNil(t, response) + assert.Equal(t, expectedUser.Id, response.User.Id) + assert.Equal(t, expectedUser.Email, response.User.Email) + assert.Equal(t, expectedUser.Name, response.User.Name) + assert.Equal(t, int64(expectedUser.Role), response.User.Role) + }) + + t.Run("InvalidToken", func(t *testing.T) { + mockTokenGenerator.EXPECT().VerifyToken("invalid-token").Return(nil, errors.New("invalid token")) + + _, err := server.ValidateUserToken(context.Background(), &proto.ValidateUserTokenRequest{Token: "invalid-token"}) + assert.Error(t, err) + }) + + t.Run("InvalidClaims", func(t *testing.T) { + claims := make(map[string]interface{}) + mockTokenGenerator.EXPECT().VerifyToken("token-without-id").Return(claims, nil).Once() + + _, err := server.ValidateUserToken(context.Background(), &proto.ValidateUserTokenRequest{Token: "token-without-id"}) + assert.Error(t, err) + }) + + t.Run("NonExistentUser", func(t *testing.T) { + nonExistingUserId := uint64(999) + claims := map[string]interface{}{"id": float64(nonExistingUserId)} + mockTokenGenerator.EXPECT().VerifyToken("valid-token").Return(claims, nil) + mockUserRepository.EXPECT().FindById(nonExistingUserId).Return(nil, errors.New("user not found")).Once() + + _, err := server.ValidateUserToken(context.Background(), &proto.ValidateUserTokenRequest{Token: "valid-token"}) + assert.Error(t, err) + }) + +} diff --git a/src/user-service/auth/_mock/mock_TokenGenerator.go b/src/user-service/auth/_mock/mock_TokenGenerator.go new file mode 100644 index 00000000..2c0bf9ec --- /dev/null +++ b/src/user-service/auth/_mock/mock_TokenGenerator.go @@ -0,0 +1,146 @@ +// Code generated by mockery v2.40.1. DO NOT EDIT. + +package auth + +import mock "github.com/stretchr/testify/mock" + +// MockTokenGenerator is an autogenerated mock type for the TokenGenerator type +type MockTokenGenerator struct { + mock.Mock +} + +type MockTokenGenerator_Expecter struct { + mock *mock.Mock +} + +func (_m *MockTokenGenerator) EXPECT() *MockTokenGenerator_Expecter { + return &MockTokenGenerator_Expecter{mock: &_m.Mock} +} + +// CreateToken provides a mock function with given fields: claims +func (_m *MockTokenGenerator) CreateToken(claims map[string]interface{}) (string, error) { + ret := _m.Called(claims) + + if len(ret) == 0 { + panic("no return value specified for CreateToken") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(map[string]interface{}) (string, error)); ok { + return rf(claims) + } + if rf, ok := ret.Get(0).(func(map[string]interface{}) string); ok { + r0 = rf(claims) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(map[string]interface{}) error); ok { + r1 = rf(claims) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockTokenGenerator_CreateToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateToken' +type MockTokenGenerator_CreateToken_Call struct { + *mock.Call +} + +// CreateToken is a helper method to define mock.On call +// - claims map[string]interface{} +func (_e *MockTokenGenerator_Expecter) CreateToken(claims interface{}) *MockTokenGenerator_CreateToken_Call { + return &MockTokenGenerator_CreateToken_Call{Call: _e.mock.On("CreateToken", claims)} +} + +func (_c *MockTokenGenerator_CreateToken_Call) Run(run func(claims map[string]interface{})) *MockTokenGenerator_CreateToken_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(map[string]interface{})) + }) + return _c +} + +func (_c *MockTokenGenerator_CreateToken_Call) Return(_a0 string, _a1 error) *MockTokenGenerator_CreateToken_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockTokenGenerator_CreateToken_Call) RunAndReturn(run func(map[string]interface{}) (string, error)) *MockTokenGenerator_CreateToken_Call { + _c.Call.Return(run) + return _c +} + +// VerifyToken provides a mock function with given fields: tokenString +func (_m *MockTokenGenerator) VerifyToken(tokenString string) (map[string]interface{}, error) { + ret := _m.Called(tokenString) + + if len(ret) == 0 { + panic("no return value specified for VerifyToken") + } + + var r0 map[string]interface{} + var r1 error + if rf, ok := ret.Get(0).(func(string) (map[string]interface{}, error)); ok { + return rf(tokenString) + } + if rf, ok := ret.Get(0).(func(string) map[string]interface{}); ok { + r0 = rf(tokenString) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]interface{}) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(tokenString) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockTokenGenerator_VerifyToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'VerifyToken' +type MockTokenGenerator_VerifyToken_Call struct { + *mock.Call +} + +// VerifyToken is a helper method to define mock.On call +// - tokenString string +func (_e *MockTokenGenerator_Expecter) VerifyToken(tokenString interface{}) *MockTokenGenerator_VerifyToken_Call { + return &MockTokenGenerator_VerifyToken_Call{Call: _e.mock.On("VerifyToken", tokenString)} +} + +func (_c *MockTokenGenerator_VerifyToken_Call) Run(run func(tokenString string)) *MockTokenGenerator_VerifyToken_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockTokenGenerator_VerifyToken_Call) Return(_a0 map[string]interface{}, _a1 error) *MockTokenGenerator_VerifyToken_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockTokenGenerator_VerifyToken_Call) RunAndReturn(run func(string) (map[string]interface{}, error)) *MockTokenGenerator_VerifyToken_Call { + _c.Call.Return(run) + return _c +} + +// NewMockTokenGenerator creates a new instance of MockTokenGenerator. 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 NewMockTokenGenerator(t interface { + mock.TestingT + Cleanup(func()) +}) *MockTokenGenerator { + mock := &MockTokenGenerator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/src/user-service/crypto/_mock/mock_Hasher.go b/src/user-service/crypto/_mock/mock_Hasher.go new file mode 100644 index 00000000..d914ded9 --- /dev/null +++ b/src/user-service/crypto/_mock/mock_Hasher.go @@ -0,0 +1,137 @@ +// Code generated by mockery v2.40.1. DO NOT EDIT. + +package crypto + +import mock "github.com/stretchr/testify/mock" + +// MockHasher is an autogenerated mock type for the Hasher type +type MockHasher struct { + mock.Mock +} + +type MockHasher_Expecter struct { + mock *mock.Mock +} + +func (_m *MockHasher) EXPECT() *MockHasher_Expecter { + return &MockHasher_Expecter{mock: &_m.Mock} +} + +// Hash provides a mock function with given fields: _a0 +func (_m *MockHasher) Hash(_a0 []byte) ([]byte, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Hash") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func([]byte) ([]byte, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockHasher_Hash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Hash' +type MockHasher_Hash_Call struct { + *mock.Call +} + +// Hash is a helper method to define mock.On call +// - _a0 []byte +func (_e *MockHasher_Expecter) Hash(_a0 interface{}) *MockHasher_Hash_Call { + return &MockHasher_Hash_Call{Call: _e.mock.On("Hash", _a0)} +} + +func (_c *MockHasher_Hash_Call) Run(run func(_a0 []byte)) *MockHasher_Hash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]byte)) + }) + return _c +} + +func (_c *MockHasher_Hash_Call) Return(_a0 []byte, _a1 error) *MockHasher_Hash_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockHasher_Hash_Call) RunAndReturn(run func([]byte) ([]byte, error)) *MockHasher_Hash_Call { + _c.Call.Return(run) + return _c +} + +// Validate provides a mock function with given fields: _a0, _a1 +func (_m *MockHasher) Validate(_a0 []byte, _a1 []byte) bool { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Validate") + } + + var r0 bool + if rf, ok := ret.Get(0).(func([]byte, []byte) bool); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// MockHasher_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate' +type MockHasher_Validate_Call struct { + *mock.Call +} + +// Validate is a helper method to define mock.On call +// - _a0 []byte +// - _a1 []byte +func (_e *MockHasher_Expecter) Validate(_a0 interface{}, _a1 interface{}) *MockHasher_Validate_Call { + return &MockHasher_Validate_Call{Call: _e.mock.On("Validate", _a0, _a1)} +} + +func (_c *MockHasher_Validate_Call) Run(run func(_a0 []byte, _a1 []byte)) *MockHasher_Validate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]byte), args[1].([]byte)) + }) + return _c +} + +func (_c *MockHasher_Validate_Call) Return(_a0 bool) *MockHasher_Validate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockHasher_Validate_Call) RunAndReturn(run func([]byte, []byte) bool) *MockHasher_Validate_Call { + _c.Call.Return(run) + return _c +} + +// NewMockHasher creates a new instance of MockHasher. 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 NewMockHasher(t interface { + mock.TestingT + Cleanup(func()) +}) *MockHasher { + mock := &MockHasher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/src/user-service/crypto/bcrypt_test.go b/src/user-service/crypto/bcrypt_test.go index 8f7da906..a1b62d74 100644 --- a/src/user-service/crypto/bcrypt_test.go +++ b/src/user-service/crypto/bcrypt_test.go @@ -12,13 +12,10 @@ func TestBcryptHasher(t *testing.T) { t.Run("Hash", func(t *testing.T) { t.Run("should return hash with salt", func(t *testing.T) { - // given password := []byte("password") - // when hash, err := hasher.Hash(password) - // then assert.NoError(t, err) assert.Len(t, hash, 60) assert.Regexp(t, regexp.MustCompile(`\$2a\$10\$(.*)`), string(hash)) @@ -27,26 +24,20 @@ func TestBcryptHasher(t *testing.T) { t.Run("Validate", func(t *testing.T) { t.Run("should return true if password matches hash", func(t *testing.T) { - // given password := []byte("password") hash := []byte("$2a$10$s3BvNfI4PZO0PhcyxK4vTeu0N3Hhxo4mMgd084ENY41q/DeXhstc6") - // when ok := hasher.Validate(password, hash) - // then assert.True(t, ok) }) t.Run("should return false if password does not match hash", func(t *testing.T) { - // given password := []byte("password") hash := []byte("$2a$10$s3BvNfI4PZO0PhcyxK4vTeu0N3Hhxo4mMgd084ENY41q/DeXhstc7") - // when ok := hasher.Validate(password, hash) - // then assert.False(t, ok) }) }) diff --git a/src/user-service/user/demo_repository.go b/src/user-service/user/demo_repository.go deleted file mode 100644 index 4c286773..00000000 --- a/src/user-service/user/demo_repository.go +++ /dev/null @@ -1,125 +0,0 @@ -package user - -import ( - "errors" - "sort" - - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/model" -) - -type DemoRepository struct { - users map[uint64]*model.User -} - -func NewDemoRepository() *DemoRepository { - return &DemoRepository{users: make(map[uint64]*model.User)} -} - -func (repo *DemoRepository) Create(user *model.User) (*model.User, error) { - var userId uint64 - if user.Id == 0 { - userId = repo.findNextAvailableID() - user.Id = userId - } else { - userId = user.Id - } - - _, found := repo.users[userId] - foundUser, err := repo.FindByEmail(user.Email) - if found || foundUser != nil { - return nil, errors.New(ErrorUserAlreadyExists) - } else if err != nil && err.Error() != ErrorUserNotFound { - return nil, err - } - - repo.users[userId] = user - - return user, nil -} - -func (repo *DemoRepository) Delete(user *model.User) error { - _, found := repo.users[user.Id] - if found { - delete(repo.users, user.Id) - return nil - } - - return errors.New(ErrorUserDeletion) -} - -func (repo *DemoRepository) FindAll() ([]*model.User, error) { - if repo.users != nil { - r := make([]*model.User, 0, len(repo.users)) - for _, v := range repo.users { - r = append(r, v) - } - - sort.Slice(r, func(i, j int) bool { - return r[i].Name < r[j].Name - }) - return r, nil - } - - return nil, errors.New(ErrorUserList) -} - -func (repo *DemoRepository) FindAllByRole(role model.Role) ([]*model.User, error) { - if repo.users != nil { - r := make([]*model.User, 0) - for _, user := range repo.users { - if user.Role == role { - r = append(r, user) - } - } - - sort.Slice(r, func(i, j int) bool { - return r[i].Name < r[j].Name - }) - return r, nil - } - - return nil, errors.New(ErrorUserList) -} - -func (repo *DemoRepository) FindByEmail(email string) (*model.User, error) { - for _, user := range repo.users { - if user.Email == email { - return user, nil - } - } - - return nil, errors.New(ErrorUserNotFound) -} - -func (repo *DemoRepository) FindById(id uint64) (*model.User, error) { - user, found := repo.users[id] - if found { - return user, nil - } - - return nil, errors.New(ErrorUserNotFound) -} - -func (repo *DemoRepository) Update(user *model.User) (*model.User, error) { - existingUser, foundError := repo.FindById(user.Id) - - if foundError != nil { - return nil, errors.New(ErrorUserUpdate) - } - - existingUser.Name = user.Name - existingUser.Email = user.Email - existingUser.Role = user.Role - - return existingUser, nil -} - -func (repo *DemoRepository) findNextAvailableID() uint64 { - var maxID uint64 - for id := range repo.users { - if id > maxID { - maxID = id - } - } - return maxID + 1 -} diff --git a/src/user-service/user/demo_repository_test.go b/src/user-service/user/demo_repository_test.go deleted file mode 100644 index 8caa3f24..00000000 --- a/src/user-service/user/demo_repository_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package user - -import ( - "hsfl.de/group6/hsfl-master-ai-cloud-engineering/user-service/user/model" - "reflect" - "sort" - "testing" -) - -func TestDemoRepository_CreateUser(t *testing.T) { - demoRepository := NewDemoRepository() - - user := model.User{ - Email: "ada.lovelace@gmail.com", - Password: []byte("123456"), - Name: "Ada Lovelace", - Role: model.Customer, - } - - // Create user with success - _, err := demoRepository.Create(&user) - if err != nil { - t.Error(err) - } - - // Check for doublet - _, err = demoRepository.Create(&user) - if err.Error() != ErrorUserAlreadyExists { - t.Error(err) - } -} - -func TestDemoRepository_FindAll(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - users := []*model.User{ - { - Id: 1, - Email: "ada.lovelace@gmail.com", - Password: []byte("123456"), - Name: "Ada Lovelace", - Role: model.Customer, - }, - { - Id: 2, - Email: "alan.turin@gmail.com", - Password: []byte("123456"), - Name: "Alan Turing", - Role: model.Customer, - }, - } - - for _, user := range users { - _, err := demoRepository.Create(user) - if err != nil { - t.Error("Failed to add prepared product for test") - } - } - - t.Run("Fetch all users", func(t *testing.T) { - fetchedUsers, err := demoRepository.FindAll() - if err != nil { - t.Error("Can't fetch users") - } - - if len(fetchedUsers) != len(users) { - t.Errorf("Unexpected product count. Expected %d, got %d", len(users), len(fetchedUsers)) - } - }) - - userTests := []struct { - name string - want *model.User - }{ - { - name: "first", - want: users[0], - }, - { - name: "second", - want: users[1], - }, - } - - for i, tt := range userTests { - t.Run("Is fetched product matching with "+tt.name+" added product?", func(t *testing.T) { - fetchedUsers, _ := demoRepository.FindAll() - sort.Slice(fetchedUsers, func(i, j int) bool { - return fetchedUsers[i].Id < fetchedUsers[j].Id - }) - if !reflect.DeepEqual(tt.want, fetchedUsers[i]) { - t.Error("Fetched product does not match original product") - } - }) - } -} - -func TestDemoRepository_FindAllByRole(t *testing.T) { - demoRepository := NewDemoRepository() - - users := []*model.User{ - { - Id: 1, - Email: "ada.lovelace@gmail.com", - Password: []byte("123456"), - Name: "Ada Lovelace", - Role: model.Customer, - }, - { - Id: 2, - Email: "alan.turin@gmail.com", - Password: []byte("123456"), - Name: "Alan Turing", - Role: model.Merchant, - }, - } - - for _, user := range users { - _, err := demoRepository.Create(user) - if err != nil { - t.Error("Failed to add prepared user for test") - } - } - - t.Run("Fetch all users by merchant role", func(t *testing.T) { - fetchedUsers, err := demoRepository.FindAllByRole(model.Merchant) - if err != nil { - t.Error("Can't fetch users") - } - - if len(fetchedUsers) != 1 { - t.Errorf("Unexpected user count. Expected 1, got %d", len(fetchedUsers)) - } - }) -} - -func TestDemoRepository_FindById(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - user := model.User{ - Id: 1, - Email: "ada.lovelace@gmail.com", - Password: []byte("123456"), - Name: "Ada Lovelace", - Role: model.Customer, - } - - _, err := demoRepository.Create(&user) - if err != nil { - t.Error("Failed to add prepare user for test") - } - - // Fetch user with existing id - fetchedUser, err := demoRepository.FindById(user.Id) - if err != nil { - t.Errorf("Can't find expected user with id %d", user.Id) - } - - // Is fetched user matching with added user? - if !reflect.DeepEqual(user, *fetchedUser) { - t.Error("Fetched user does not match original user") - } - - // Non-existing user test - _, err = demoRepository.FindById(42) - if err.Error() != "user could not be found" { - t.Error(err) - } -} - -func TestDemoRepository_FindByEmail(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - user := model.User{ - Id: 1, - Email: "ada.lovelace@gmail.com", - Password: []byte("123456"), - Name: "Ada Lovelace", - Role: model.Customer, - } - - _, err := demoRepository.Create(&user) - if err != nil { - t.Error("Failed to add prepare user for test") - } - - // Fetch user with existing email - fetchedUser, err := demoRepository.FindByEmail(user.Email) - if err != nil { - t.Errorf("Can't find expected user with email %s", user.Email) - } - - // Is fetched user matching with added user? - if !reflect.DeepEqual(user, *fetchedUser) { - t.Error("Fetched user does not match original user") - } - - // Non-existing user test - _, err = demoRepository.FindByEmail("alan.turing@gmail.com") - if err.Error() != "user could not be found" { - t.Error(err) - } -} - -func TestDemoRepository_Update(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - user := model.User{ - Id: 1, - Email: "ada.lovelace@gmail.com", - Password: []byte("123456"), - Name: "Ada Lovelace", - Role: model.Customer, - } - - fetchedUser, err := demoRepository.Create(&user) - if err != nil { - t.Error("Failed to add prepare user for test") - } - - fetchedUser.Name = "Alan Turing" - updatedUser, err := demoRepository.Update(fetchedUser) - - // Check if returned user has the updated name - if fetchedUser.Name != updatedUser.Name { - t.Error("Failed to update user") - } -} - -func TestDemoRepository_Delete(t *testing.T) { - // Prepare test - demoRepository := NewDemoRepository() - - user := model.User{ - Id: 1, - Email: "ada.lovelace@gmail.com", - Password: []byte("123456"), - Name: "Ada Lovelace", - Role: model.Customer, - } - - fetchedUser, err := demoRepository.Create(&user) - if err != nil { - t.Error("Failed to add prepare user for test") - } - - // Test for deletion - err = demoRepository.Delete(fetchedUser) - if err != nil { - t.Errorf("Failed to delete user with id %d", user.Id) - } - - // Fetch user with existing id - fetchedUser, err = demoRepository.FindById(user.Id) - if err.Error() != "user could not be found" { - t.Errorf("User with id %d was not deleted", user.Id) - } - - // Try to delete non-existing user - fakeUser := model.User{ - Id: 42, - Email: "alan.turin@gmail.com", - Password: []byte("123456"), - Name: "Alan Turing", - Role: model.Customer, - } - - // Test for deletion - err = demoRepository.Delete(&fakeUser) - if err.Error() != "user could not be deleted" { - t.Errorf("User with id %d was deleted", user.Id) - } -} diff --git a/utils/http-stress-test/config.yaml b/utils/http-stress-test/config.yaml index 92944752..c9317f14 100644 --- a/utils/http-stress-test/config.yaml +++ b/utils/http-stress-test/config.yaml @@ -1,10 +1,14 @@ phases: - targetrps: 0 timeidx: 0 + - targetrps: 100 + timeidx: 15 - targetrps: 500 - timeidx: 10 - - targetrps: 500 - timeidx: 20 + timeidx: 30 + - targetrps: 100 + timeidx: 50 + - targetrps: 0 + timeidx: 70 targets: - - url: https://google.de:443 + - url: http://onyxmoon.me users: 10 diff --git a/utils/http-stress-test/metrics/metrics.go b/utils/http-stress-test/metrics/metrics.go index 4ba1bc7a..5c1e01f0 100644 --- a/utils/http-stress-test/metrics/metrics.go +++ b/utils/http-stress-test/metrics/metrics.go @@ -127,7 +127,3 @@ func (m *Metrics) DisplayMetrics(ctx context.Context) { } } } - -func formatInt64(i int64) string { - return fmt.Sprintf("%d", i) -} diff --git a/utils/http-stress-test/network/tcpclient.go b/utils/http-stress-test/network/tcpclient.go index 2b78156a..58de8eac 100644 --- a/utils/http-stress-test/network/tcpclient.go +++ b/utils/http-stress-test/network/tcpclient.go @@ -12,10 +12,10 @@ func NewTcpClient() *TCPClient { return &TCPClient{} } -func (c *TCPClient) Send(targetURL string) { +func (c *TCPClient) Send(targetURL string) error { parsedURL, err := url.Parse(targetURL) if err != nil { - return + return err } target := parsedURL.Host path := parsedURL.Path @@ -25,14 +25,14 @@ func (c *TCPClient) Send(targetURL string) { conn, err := net.Dial("tcp", target) if err != nil { - //fmt.Printf("Error connecting to %s: %v\n", target, err) - return + return err } request := fmt.Sprintf("GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", path, target) _, err = conn.Write([]byte(request)) if err != nil { - //fmt.Printf("Error sending request to %s: %v\n", target, err) - return + return err } + + return nil } diff --git a/utils/http-stress-test/tester/tester.go b/utils/http-stress-test/tester/tester.go index b2cc72c1..331f3e56 100644 --- a/utils/http-stress-test/tester/tester.go +++ b/utils/http-stress-test/tester/tester.go @@ -120,12 +120,13 @@ func (t *Tester) NewRunUser(wg *sync.WaitGroup, metrics *metrics.Metrics, timePo } } else { fastclient := network.NewTcpClient() - go fastclient.Send(targetURL) + err := fastclient.Send(targetURL) responseTime := time.Since(startTime) requestCount++ if metrics != nil { - metrics.RecordResponse(responseTime, true) + success := err == nil + metrics.RecordResponse(responseTime, success) } }