From 6c1364f402aaccc6435bc5167b4672ba62071b81 Mon Sep 17 00:00:00 2001 From: Dearsh Oberoi Date: Mon, 23 Feb 2026 15:30:47 +0530 Subject: [PATCH 1/2] refactor(obligationmap): obligation map endpoints removed Signed-off-by: Dearsh Oberoi --- cmd/laas/docs/docs.go | 307 ---------------------------- cmd/laas/docs/swagger.json | 307 ---------------------------- cmd/laas/docs/swagger.yaml | 204 ------------------- pkg/api/api.go | 17 -- pkg/api/obligationmap.go | 374 ---------------------------------- pkg/models/types.go | 31 --- tests/obligation_maps_test.go | 295 --------------------------- 7 files changed, 1535 deletions(-) delete mode 100644 pkg/api/obligationmap.go delete mode 100644 tests/obligation_maps_test.go diff --git a/cmd/laas/docs/docs.go b/cmd/laas/docs/docs.go index 764cb00..f33cc11 100644 --- a/cmd/laas/docs/docs.go +++ b/cmd/laas/docs/docs.go @@ -844,218 +844,6 @@ const docTemplate = `{ } } }, - "/obligation_maps/license/{id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [], - "{}": [] - } - ], - "description": "Get obligation maps for a given license id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Obligations" - ], - "summary": "Get maps for a license", - "operationId": "GetObligationMapByLicenseId", - "parameters": [ - { - "type": "string", - "description": "id of the license", - "name": "license", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.ObligationMapResponse" - } - }, - "404": { - "description": "No license with given id found", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - } - } - } - }, - "/obligation_maps/obligation/{id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [], - "{}": [] - } - ], - "description": "Get obligation maps for a given obligation id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Obligations" - ], - "summary": "Get maps for an obligation", - "operationId": "GetObligationMapByObligationId", - "parameters": [ - { - "type": "string", - "description": "Id of the obligation", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.ObligationMapResponse" - } - }, - "404": { - "description": "No obligation with given id found", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - } - } - } - }, - "/obligation_maps/obligation/{id}/license": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Replaces the license list of an obligation id with the given list in the obligation map.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Obligations" - ], - "summary": "Change license list", - "operationId": "UpdateLicenseInObligationMap", - "parameters": [ - { - "type": "string", - "description": "Id of the obligation", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Ids of the licenses to be in map", - "name": "Ids", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.LicenseListInput" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.ObligationMapResponse" - } - }, - "400": { - "description": "Invalid json body", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - }, - "404": { - "description": "No license or obligation found.", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - } - } - }, - "patch": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Add or remove licenses from obligation map for a given obligation id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Obligations" - ], - "summary": "Add or remove licenses from obligation map", - "operationId": "PatchObligationMap", - "parameters": [ - { - "type": "string", - "description": "Id of the obligation", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "License ids with action", - "name": "license_maps", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.LicenseMapInput" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.ObligationMapResponse" - } - }, - "400": { - "description": "Invalid json body", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - }, - "404": { - "description": "No license or obligation found.", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - }, - "500": { - "description": "Failure to insert new maps", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - } - } - } - }, "/obligations": { "get": { "security": [ @@ -2903,41 +2691,6 @@ const docTemplate = `{ } } }, - "models.LicenseListInput": { - "type": "object", - "properties": { - "ids": { - "type": "array", - "items": { - "type": "object" - } - } - } - }, - "models.LicenseMapElement": { - "type": "object", - "properties": { - "add": { - "type": "boolean", - "example": true - }, - "id": { - "type": "string", - "example": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" - } - } - }, - "models.LicenseMapInput": { - "type": "object", - "properties": { - "map": { - "type": "array", - "items": { - "$ref": "#/definitions/models.LicenseMapElement" - } - } - } - }, "models.LicensePreview": { "type": "object", "properties": { @@ -3290,66 +3043,6 @@ const docTemplate = `{ } } }, - "models.ObligationMapLicenseFormat": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" - }, - "shortname": { - "type": "string", - "example": "MIT" - } - } - }, - "models.ObligationMapResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/models.ObligationMapUser" - } - }, - "paginationmeta": { - "$ref": "#/definitions/models.PaginationMeta" - }, - "status": { - "type": "integer", - "example": 200 - } - } - }, - "models.ObligationMapUser": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" - }, - "licenses": { - "type": "array", - "items": { - "$ref": "#/definitions/models.ObligationMapLicenseFormat" - } - }, - "topic": { - "type": "string", - "example": "copyleft" - }, - "type": { - "type": "string", - "enum": [ - "obligation", - "restriction", - "risk", - "right" - ], - "example": "obligation" - } - } - }, "models.ObligationPreview": { "type": "object", "properties": { diff --git a/cmd/laas/docs/swagger.json b/cmd/laas/docs/swagger.json index 9894923..01eea22 100644 --- a/cmd/laas/docs/swagger.json +++ b/cmd/laas/docs/swagger.json @@ -837,218 +837,6 @@ } } }, - "/obligation_maps/license/{id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [], - "{}": [] - } - ], - "description": "Get obligation maps for a given license id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Obligations" - ], - "summary": "Get maps for a license", - "operationId": "GetObligationMapByLicenseId", - "parameters": [ - { - "type": "string", - "description": "id of the license", - "name": "license", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.ObligationMapResponse" - } - }, - "404": { - "description": "No license with given id found", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - } - } - } - }, - "/obligation_maps/obligation/{id}": { - "get": { - "security": [ - { - "ApiKeyAuth": [], - "{}": [] - } - ], - "description": "Get obligation maps for a given obligation id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Obligations" - ], - "summary": "Get maps for an obligation", - "operationId": "GetObligationMapByObligationId", - "parameters": [ - { - "type": "string", - "description": "Id of the obligation", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.ObligationMapResponse" - } - }, - "404": { - "description": "No obligation with given id found", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - } - } - } - }, - "/obligation_maps/obligation/{id}/license": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Replaces the license list of an obligation id with the given list in the obligation map.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Obligations" - ], - "summary": "Change license list", - "operationId": "UpdateLicenseInObligationMap", - "parameters": [ - { - "type": "string", - "description": "Id of the obligation", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Ids of the licenses to be in map", - "name": "Ids", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.LicenseListInput" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.ObligationMapResponse" - } - }, - "400": { - "description": "Invalid json body", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - }, - "404": { - "description": "No license or obligation found.", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - } - } - }, - "patch": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Add or remove licenses from obligation map for a given obligation id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Obligations" - ], - "summary": "Add or remove licenses from obligation map", - "operationId": "PatchObligationMap", - "parameters": [ - { - "type": "string", - "description": "Id of the obligation", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "License ids with action", - "name": "license_maps", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/models.LicenseMapInput" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/models.ObligationMapResponse" - } - }, - "400": { - "description": "Invalid json body", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - }, - "404": { - "description": "No license or obligation found.", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - }, - "500": { - "description": "Failure to insert new maps", - "schema": { - "$ref": "#/definitions/models.LicenseError" - } - } - } - } - }, "/obligations": { "get": { "security": [ @@ -2896,41 +2684,6 @@ } } }, - "models.LicenseListInput": { - "type": "object", - "properties": { - "ids": { - "type": "array", - "items": { - "type": "object" - } - } - } - }, - "models.LicenseMapElement": { - "type": "object", - "properties": { - "add": { - "type": "boolean", - "example": true - }, - "id": { - "type": "string", - "example": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" - } - } - }, - "models.LicenseMapInput": { - "type": "object", - "properties": { - "map": { - "type": "array", - "items": { - "$ref": "#/definitions/models.LicenseMapElement" - } - } - } - }, "models.LicensePreview": { "type": "object", "properties": { @@ -3283,66 +3036,6 @@ } } }, - "models.ObligationMapLicenseFormat": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" - }, - "shortname": { - "type": "string", - "example": "MIT" - } - } - }, - "models.ObligationMapResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/models.ObligationMapUser" - } - }, - "paginationmeta": { - "$ref": "#/definitions/models.PaginationMeta" - }, - "status": { - "type": "integer", - "example": 200 - } - } - }, - "models.ObligationMapUser": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" - }, - "licenses": { - "type": "array", - "items": { - "$ref": "#/definitions/models.ObligationMapLicenseFormat" - } - }, - "topic": { - "type": "string", - "example": "copyleft" - }, - "type": { - "type": "string", - "enum": [ - "obligation", - "restriction", - "risk", - "right" - ], - "example": "obligation" - } - } - }, "models.ObligationPreview": { "type": "object", "properties": { diff --git a/cmd/laas/docs/swagger.yaml b/cmd/laas/docs/swagger.yaml index 7928f6e..bd13a4d 100644 --- a/cmd/laas/docs/swagger.yaml +++ b/cmd/laas/docs/swagger.yaml @@ -288,29 +288,6 @@ definitions: example: 200 type: integer type: object - models.LicenseListInput: - properties: - ids: - items: - type: object - type: array - type: object - models.LicenseMapElement: - properties: - add: - example: true - type: boolean - id: - example: f81d4fae-7dec-11d0-a765-00a0c91e6bf6 - type: string - type: object - models.LicenseMapInput: - properties: - map: - items: - $ref: '#/definitions/models.LicenseMapElement' - type: array - type: object models.LicensePreview: properties: id: @@ -559,48 +536,6 @@ definitions: example: 200 type: integer type: object - models.ObligationMapLicenseFormat: - properties: - id: - example: f81d4fae-7dec-11d0-a765-00a0c91e6bf6 - type: string - shortname: - example: MIT - type: string - type: object - models.ObligationMapResponse: - properties: - data: - items: - $ref: '#/definitions/models.ObligationMapUser' - type: array - paginationmeta: - $ref: '#/definitions/models.PaginationMeta' - status: - example: 200 - type: integer - type: object - models.ObligationMapUser: - properties: - id: - example: f81d4fae-7dec-11d0-a765-00a0c91e6bf6 - type: string - licenses: - items: - $ref: '#/definitions/models.ObligationMapLicenseFormat' - type: array - topic: - example: copyleft - type: string - type: - enum: - - obligation - - restriction - - risk - - right - example: obligation - type: string - type: object models.ObligationPreview: properties: id: @@ -1526,145 +1461,6 @@ paths: summary: Login tags: - Users - /obligation_maps/license/{id}: - get: - consumes: - - application/json - description: Get obligation maps for a given license id - operationId: GetObligationMapByLicenseId - parameters: - - description: id of the license - in: path - name: license - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.ObligationMapResponse' - "404": - description: No license with given id found - schema: - $ref: '#/definitions/models.LicenseError' - security: - - '{}': [] - ApiKeyAuth: [] - summary: Get maps for a license - tags: - - Obligations - /obligation_maps/obligation/{id}: - get: - consumes: - - application/json - description: Get obligation maps for a given obligation id - operationId: GetObligationMapByObligationId - parameters: - - description: Id of the obligation - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.ObligationMapResponse' - "404": - description: No obligation with given id found - schema: - $ref: '#/definitions/models.LicenseError' - security: - - '{}': [] - ApiKeyAuth: [] - summary: Get maps for an obligation - tags: - - Obligations - /obligation_maps/obligation/{id}/license: - patch: - consumes: - - application/json - description: Add or remove licenses from obligation map for a given obligation - id - operationId: PatchObligationMap - parameters: - - description: Id of the obligation - in: path - name: id - required: true - type: string - - description: License ids with action - in: body - name: license_maps - required: true - schema: - $ref: '#/definitions/models.LicenseMapInput' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.ObligationMapResponse' - "400": - description: Invalid json body - schema: - $ref: '#/definitions/models.LicenseError' - "404": - description: No license or obligation found. - schema: - $ref: '#/definitions/models.LicenseError' - "500": - description: Failure to insert new maps - schema: - $ref: '#/definitions/models.LicenseError' - security: - - ApiKeyAuth: [] - summary: Add or remove licenses from obligation map - tags: - - Obligations - put: - consumes: - - application/json - description: Replaces the license list of an obligation id with the given list - in the obligation map. - operationId: UpdateLicenseInObligationMap - parameters: - - description: Id of the obligation - in: path - name: id - required: true - type: string - - description: Ids of the licenses to be in map - in: body - name: Ids - required: true - schema: - $ref: '#/definitions/models.LicenseListInput' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/models.ObligationMapResponse' - "400": - description: Invalid json body - schema: - $ref: '#/definitions/models.LicenseError' - "404": - description: No license or obligation found. - schema: - $ref: '#/definitions/models.LicenseError' - security: - - ApiKeyAuth: [] - summary: Change license list - tags: - - Obligations /obligations: get: consumes: diff --git a/pkg/api/api.go b/pkg/api/api.go index 37e342a..92bd8c6 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -156,13 +156,6 @@ func Router() *gin.Engine { obligations.DELETE("/classifications/:classification", middleware.RoleBasedAccessMiddleware([]string{"ADMIN"}), DeleteObligationClassification) obligations.POST("/similarity", getSimilarObligations) } - obMap := authorizedv1.Group("/obligation_maps") - { - obMap.GET("obligation/:id", GetObligationMapByObligationId) - obMap.GET("license/:id", GetObligationMapByLicenseId) - obMap.PATCH("obligation/:id/license", PatchObligationMap) - obMap.PUT("obligation/:id/license", UpdateLicenseInObligationMap) - } audit := authorizedv1.Group("/audits") { audit.GET("", GetAllAudit) @@ -205,11 +198,6 @@ func Router() *gin.Engine { obligations.GET("/types", GetAllObligationType) obligations.GET("/classifications", GetAllObligationClassification) } - obMap := unAuthorizedv1.Group("/obligation_maps") - { - obMap.GET("obligation/:id", GetObligationMapByObligationId) - obMap.GET("license/:id", GetObligationMapByLicenseId) - } audit := unAuthorizedv1.Group("/audits") { audit.GET("", GetAllAudit) @@ -276,11 +264,6 @@ func Router() *gin.Engine { obligations.DELETE("/classifications/:classification", middleware.RoleBasedAccessMiddleware([]string{"ADMIN"}), DeleteObligationClassification) obligations.POST("/similarity", getSimilarObligations) } - obMap := authorizedv1.Group("/obligation_maps") - { - obMap.PATCH("obligation/:id/license", PatchObligationMap) - obMap.PUT("obligation/:id/license", UpdateLicenseInObligationMap) - } oidcClient := authorizedv1.Group("/oidcClients") { oidcClient.GET("", GetUserOidcClients) diff --git a/pkg/api/obligationmap.go b/pkg/api/obligationmap.go deleted file mode 100644 index 94488e7..0000000 --- a/pkg/api/obligationmap.go +++ /dev/null @@ -1,374 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Siemens AG -// SPDX-FileContributor: Gaurav Mishra -// SPDX-FileContributor: Dearsh Oberoi -// -// SPDX-License-Identifier: GPL-2.0-only - -package api - -import ( - "fmt" - "net/http" - "time" - - "golang.org/x/exp/slices" - "gorm.io/gorm" - - "github.com/fossology/LicenseDb/pkg/db" - "github.com/fossology/LicenseDb/pkg/models" - "github.com/fossology/LicenseDb/pkg/utils" - "github.com/gin-gonic/gin" - "github.com/google/uuid" -) - -// GetObligationMapByObligationId retrieves obligation maps for a given obligation id -// -// @Summary Get maps for an obligation -// @Description Get obligation maps for a given obligation id -// @Id GetObligationMapByObligationId -// @Tags Obligations -// @Accept json -// @Produce json -// @Param id path string true "Id of the obligation" -// @Success 200 {object} models.ObligationMapResponse -// @Failure 404 {object} models.LicenseError "No obligation with given id found" -// @Security ApiKeyAuth || {} -// @Router /obligation_maps/obligation/{id} [get] -func GetObligationMapByObligationId(c *gin.Context) { - var obligation models.Obligation - var resObMap models.ObligationMapUser - var list []models.ObligationMapLicenseFormat - - obligationId, err := uuid.Parse(c.Param("id")) - if err != nil { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: fmt.Sprintf("no obligation with id '%s' exists", obligationId.String()), - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return - } - - if err := db.DB.Joins("Classification").Joins("Type").Preload("Licenses").Where(models.Obligation{Id: obligationId}).First(&obligation).Error; err != nil { - er := models.LicenseError{ - Status: http.StatusNotFound, - Message: fmt.Sprintf("obligation with id '%s' not found", obligationId.String()), - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusNotFound, er) - return - } - - for _, lic := range obligation.Licenses { - list = append(list, models.ObligationMapLicenseFormat{ - Id: lic.Id, - Shortname: *lic.Shortname, - }) - } - - resObMap = models.ObligationMapUser{ - Id: obligation.Id, - Topic: *obligation.Topic, - Type: (*obligation.Type).Type, - Licenses: list, - } - - res := models.ObligationMapResponse{ - Data: []models.ObligationMapUser{resObMap}, - Status: http.StatusOK, - Meta: models.PaginationMeta{ - ResourceCount: 1, - }, - } - c.JSON(http.StatusOK, res) -} - -// GetObligationMapByLicenseId retrieves obligation maps for given license id -// -// @Summary Get maps for a license -// @Description Get obligation maps for a given license id -// @Id GetObligationMapByLicenseId -// @Tags Obligations -// @Accept json -// @Produce json -// @Param license path string true "id of the license" -// @Success 200 {object} models.ObligationMapResponse -// @Failure 404 {object} models.LicenseError "No license with given id found" -// @Security ApiKeyAuth || {} -// @Router /obligation_maps/license/{id} [get] -func GetObligationMapByLicenseId(c *gin.Context) { - var license models.LicenseDB - var resObMapList []models.ObligationMapUser - - licenseId, err := uuid.Parse(c.Param("id")) - if err != nil { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: fmt.Sprintf("no license with id '%s' exists", licenseId.String()), - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return - } - - if err := db.DB.Preload("Obligations").Preload("Obligations.Type").Preload("Obligations.Classification").Where(models.LicenseDB{Id: licenseId}).First(&license).Error; err != nil { - er := models.LicenseError{ - Status: http.StatusNotFound, - Message: fmt.Sprintf("no license with id '%s' exists", licenseId.String()), - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusNotFound, er) - return - } - - for _, ob := range license.Obligations { - resObMapList = append(resObMapList, models.ObligationMapUser{ - Id: ob.Id, - Type: (*ob.Type).Type, - Topic: *ob.Topic, - Licenses: []models.ObligationMapLicenseFormat{ - { - Id: license.Id, - Shortname: *license.Shortname, - }, - }, - }) - } - - res := models.ObligationMapResponse{ - Data: resObMapList, - Status: http.StatusOK, - Meta: models.PaginationMeta{ - ResourceCount: len(resObMapList), - }, - } - c.JSON(http.StatusOK, res) -} - -// PatchObligationMap Add or remove licenses from obligation map for a given obligation id -// -// @Summary Add or remove licenses from obligation map -// @Description Add or remove licenses from obligation map for a given obligation id -// @Id PatchObligationMap -// @Tags Obligations -// @Accept json -// @Produce json -// @Param id path string true "Id of the obligation" -// @Param license_maps body models.LicenseMapInput true "License ids with action" -// @Success 200 {object} models.ObligationMapResponse -// @Failure 400 {object} models.LicenseError "Invalid json body" -// @Failure 404 {object} models.LicenseError "No license or obligation found." -// @Failure 500 {object} models.LicenseError "Failure to insert new maps" -// @Security ApiKeyAuth -// @Router /obligation_maps/obligation/{id}/license [patch] -func PatchObligationMap(c *gin.Context) { - var obligation models.Obligation - var obMapInput models.LicenseMapInput - var removeLicenses, insertLicenses []uuid.UUID - - obligationId, err := uuid.Parse(c.Param("id")) - if err != nil { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: fmt.Sprintf("no obligation with id '%s' exists", obligationId.String()), - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return - } - - if err := c.ShouldBindJSON(&obMapInput); err != nil { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "invalid json body", - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return - } - - if err := db.DB.Preload("Licenses").Joins("Type").Where(models.Obligation{Id: obligationId}).First(&obligation).Error; err != nil { - er := models.LicenseError{ - Status: http.StatusNotFound, - Message: fmt.Sprintf("obligation with id '%s' not found", obligationId.String()), - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusNotFound, er) - return - } - - for _, lic := range obMapInput.MapInput { - if lic.Add { - found := false - for _, l := range obligation.Licenses { - if lic.Id == l.Id { - found = true - break - } - } - if !found { - insertLicenses = append(insertLicenses, lic.Id) - } - } else { - removeLicenses = append(removeLicenses, lic.Id) - } - } - - userId := c.MustGet("userId").(uuid.UUID) - _ = db.DB.Transaction(func(tx *gorm.DB) error { - newLicenseAssociations, errs := utils.PerformObligationMapActions(tx, userId, &obligation, removeLicenses, insertLicenses) - if len(errs) != 0 { - var combinedErrors string - for _, err := range errs { - if err != nil { - combinedErrors += fmt.Sprintf("%s\n", err) - } - } - er := models.LicenseError{ - Status: http.StatusInternalServerError, - Message: "Unable to add/remove some of the licenses", - Error: combinedErrors, - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusInternalServerError, er) - return nil - } - - obMap := &models.ObligationMapUser{ - Topic: *obligation.Topic, - Type: (*obligation.Type).Type, - Licenses: newLicenseAssociations, - } - - res := models.ObligationMapResponse{ - Data: []models.ObligationMapUser{*obMap}, - Status: http.StatusOK, - Meta: models.PaginationMeta{ - ResourceCount: 1, - }, - } - - c.JSON(http.StatusOK, res) - - return nil - }) -} - -// UpdateLicenseInObligationMap Update license list of an obligation map -// -// @Summary Change license list -// @Description Replaces the license list of an obligation id with the given list in the obligation map. -// @Id UpdateLicenseInObligationMap -// @Tags Obligations -// @Accept json -// @Produce json -// @Param id path string true "Id of the obligation" -// @Param Ids body models.LicenseListInput true "Ids of the licenses to be in map" -// @Success 200 {object} models.ObligationMapResponse -// @Failure 400 {object} models.LicenseError "Invalid json body" -// @Failure 404 {object} models.LicenseError "No license or obligation found." -// @Security ApiKeyAuth -// @Router /obligation_maps/obligation/{id}/license [put] -func UpdateLicenseInObligationMap(c *gin.Context) { - var obligation models.Obligation - var obMapInput models.LicenseListInput - var removeLicenses, insertLicenses []uuid.UUID - - if err := c.ShouldBindJSON(&obMapInput); err != nil { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "invalid json body", - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return - } - - obligationId, err := uuid.Parse(c.Param("id")) - if err != nil { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: fmt.Sprintf("no obligation with id '%s' exists", obligationId.String()), - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return - } - - if err := db.DB.Preload("Licenses").Joins("Type").Where(models.Obligation{Id: obligationId}).First(&obligation).Error; err != nil { - er := models.LicenseError{ - Status: http.StatusNotFound, - Message: fmt.Sprintf("obligation with id '%s' not found", obligationId.String()), - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusNotFound, er) - return - } - - obMapInput.LicenseIds = slices.Compact(obMapInput.LicenseIds) - - utils.GenerateDiffForReplacingLicenses(&obligation, obMapInput.LicenseIds, &removeLicenses, &insertLicenses) - - userId := c.MustGet("userId").(uuid.UUID) - _ = db.DB.Transaction(func(tx *gorm.DB) error { - newLicenseAssociations, errs := utils.PerformObligationMapActions(tx, userId, &obligation, removeLicenses, insertLicenses) - if len(errs) != 0 { - var combinedErrors string - for _, err := range errs { - if err != nil { - combinedErrors += fmt.Sprintf("%s\n", err) - } - } - er := models.LicenseError{ - Status: http.StatusInternalServerError, - Message: "Unable to add/remove some of the licenses", - Error: combinedErrors, - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusInternalServerError, er) - return nil - } - - obMap := &models.ObligationMapUser{ - Id: obligation.Id, - Topic: *obligation.Topic, - Type: (*obligation.Type).Type, - Licenses: newLicenseAssociations, - } - - res := models.ObligationMapResponse{ - Data: []models.ObligationMapUser{*obMap}, - Status: http.StatusOK, - Meta: models.PaginationMeta{ - ResourceCount: 1, - }, - } - - c.JSON(http.StatusOK, res) - - return nil - }) -} diff --git a/pkg/models/types.go b/pkg/models/types.go index 91f5b02..7efebe7 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -261,37 +261,6 @@ type ObligationMapLicenseFormat struct { Shortname string `json:"shortname" example:"MIT"` } -// ObligationMapUser Structure with obligation topic and license shortname list, a simple representation for user. -type ObligationMapUser struct { - Id uuid.UUID `json:"id" example:"f81d4fae-7dec-11d0-a765-00a0c91e6bf6" swaggertype:"string"` - Topic string `json:"topic" example:"copyleft"` - Type string `json:"type" example:"obligation" enums:"obligation,restriction,risk,right"` - Licenses []ObligationMapLicenseFormat `json:"licenses"` -} - -// LicenseShortnamesInput represents the input format for adding/removing licenses from obligation map. -type LicenseListInput struct { - LicenseIds []uuid.UUID `json:"ids" swaggertype:"array,object"` -} - -// LicenseMapElement Element to hold license -type LicenseMapElement struct { - Id uuid.UUID `json:"id" example:"f81d4fae-7dec-11d0-a765-00a0c91e6bf6" swaggertype:"string"` - Add bool `json:"add" example:"true"` -} - -// LicenseMapInput List of elements to be read as input by API -type LicenseMapInput struct { - MapInput []LicenseMapElement `json:"map"` -} - -// ObligationMapResponse response format for obligation map data. -type ObligationMapResponse struct { - Status int `json:"status" example:"200"` - Data []ObligationMapUser `json:"data"` - Meta PaginationMeta `json:"paginationmeta"` -} - // ObligationImportRequest represents the request body structure for import obligation type ObligationImportRequest struct { ObligationFile string `form:"file"` diff --git a/tests/obligation_maps_test.go b/tests/obligation_maps_test.go deleted file mode 100644 index 1011b87..0000000 --- a/tests/obligation_maps_test.go +++ /dev/null @@ -1,295 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Krishi Agrawal -// -// SPDX-License-Identifier: GPL-2.0-only - -package test - -import ( - "encoding/json" - "net/http" - "testing" - - "github.com/fossology/LicenseDb/pkg/models" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func TestGetObligationMapByObligationId(t *testing.T) { - loginAs(t, "admin") - - licenseW := makeRequest("GET", "/licenses", nil, true) - assert.Equal(t, http.StatusOK, licenseW.Code) - - var licenseRes models.LicenseResponse - if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { - t.Fatalf("Failed to parse license response: %v", err) - } - - if len(licenseRes.Data) == 0 { - t.Skip("No licenses available for testing") - } - - dto := models.ObligationCreateDTO{ - Topic: "test-obligation-map", - Type: "RIGHT", - Text: "Test obligation text", - Classification: "GREEN", - Comment: ptr("Test comment"), - Active: ptr(true), - TextUpdatable: ptr(false), - Category: ptr("GENERAL"), - LicenseIds: []uuid.UUID{licenseRes.Data[0].Id}, - } - createW := makeRequest("POST", "/obligations", dto, true) - assert.Equal(t, http.StatusCreated, createW.Code) - - var createRes models.ObligationResponse - if err := json.Unmarshal(createW.Body.Bytes(), &createRes); err != nil { - t.Fatalf("Failed to parse create response: %v", err) - } - if len(createRes.Data) == 0 { - t.Fatal("No obligation returned in create response") - } - obligationId := createRes.Data[0].Id - - t.Run("getObligationMapByObligationId", func(t *testing.T) { - w := makeRequest("GET", "/obligation_maps/obligation/"+obligationId.String(), nil, true) - assert.Equal(t, http.StatusOK, w.Code) - - var res models.ObligationMapResponse - if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { - t.Errorf("Error unmarshalling JSON: %v", err) - return - } - assert.Equal(t, http.StatusOK, res.Status) - if len(res.Data) > 0 { - assert.Equal(t, dto.Topic, res.Data[0].Topic) - assert.GreaterOrEqual(t, len(res.Data[0].Licenses), 0) - } - }) - - t.Run("getObligationMapForNonExistingObligation", func(t *testing.T) { - w := makeRequest("GET", "/obligation_maps/obligation/"+uuid.New().String(), nil, true) - assert.Equal(t, http.StatusNotFound, w.Code) - }) - - t.Run("getObligationMapWithInvalidId", func(t *testing.T) { - w := makeRequest("GET", "/obligation_maps/obligation/invalid-id", nil, true) - assert.Equal(t, http.StatusBadRequest, w.Code) - }) -} - -func TestGetObligationMapByLicenseId(t *testing.T) { - licenseW := makeRequest("GET", "/licenses", nil, true) - assert.Equal(t, http.StatusOK, licenseW.Code) - - var licenseRes models.LicenseResponse - if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { - t.Fatalf("Failed to parse license response: %v", err) - } - - if len(licenseRes.Data) == 0 { - t.Skip("No licenses available for testing") - } - licenseId := licenseRes.Data[0].Id - - t.Run("getObligationMapByLicenseId", func(t *testing.T) { - w := makeRequest("GET", "/obligation_maps/license/"+licenseId.String(), nil, true) - assert.Equal(t, http.StatusOK, w.Code) - - var res models.ObligationMapResponse - if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { - t.Errorf("Error unmarshalling JSON: %v", err) - return - } - assert.Equal(t, http.StatusOK, res.Status) - assert.GreaterOrEqual(t, len(res.Data), 0) - }) - - t.Run("getObligationMapForNonExistingLicense", func(t *testing.T) { - w := makeRequest("GET", "/obligation_maps/license/"+uuid.New().String(), nil, true) - assert.Equal(t, http.StatusNotFound, w.Code) - }) - - t.Run("getObligationMapWithInvalidId", func(t *testing.T) { - w := makeRequest("GET", "/obligation_maps/license/invalid-id", nil, true) - assert.Equal(t, http.StatusBadRequest, w.Code) - }) -} - -func TestPatchObligationMap(t *testing.T) { - dto := models.ObligationCreateDTO{ - Topic: "test-patch-obligation-map", - Type: "RIGHT", - Text: "Test obligation text", - Classification: "GREEN", - Comment: ptr("Test comment"), - Active: ptr(true), - TextUpdatable: ptr(false), - Category: ptr("GENERAL"), - } - createW := makeRequest("POST", "/obligations", dto, true) - assert.Equal(t, http.StatusCreated, createW.Code) - - var createRes models.ObligationResponse - if err := json.Unmarshal(createW.Body.Bytes(), &createRes); err != nil { - t.Fatalf("Failed to parse create response: %v", err) - } - if len(createRes.Data) == 0 { - t.Fatal("No obligation returned in create response") - } - obligationId := createRes.Data[0].Id - - licenseW := makeRequest("GET", "/licenses", nil, true) - assert.Equal(t, http.StatusOK, licenseW.Code) - - var licenseRes models.LicenseResponse - if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { - t.Fatalf("Failed to parse license response: %v", err) - } - - if len(licenseRes.Data) == 0 { - t.Skip("No licenses available for testing") - } - licenseId := licenseRes.Data[0].Id - - t.Run("patchObligationMapAddLicense", func(t *testing.T) { - mapInput := models.LicenseMapInput{ - MapInput: []models.LicenseMapElement{ - { - Id: licenseId, - Add: true, - }, - }, - } - - w := makeRequest("PATCH", "/obligation_maps/obligation/"+obligationId.String()+"/license", mapInput, true) - assert.Equal(t, http.StatusOK, w.Code) - - var res models.ObligationMapResponse - if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { - t.Errorf("Error unmarshalling JSON: %v", err) - return - } - assert.Equal(t, http.StatusOK, res.Status) - }) - - t.Run("patchObligationMapRemoveLicense", func(t *testing.T) { - mapInputAdd := models.LicenseMapInput{ - MapInput: []models.LicenseMapElement{ - { - Id: licenseId, - Add: true, - }, - }, - } - _ = makeRequest("PATCH", "/obligation_maps/obligation/"+obligationId.String()+"/license", mapInputAdd, true) - - mapInputRemove := models.LicenseMapInput{ - MapInput: []models.LicenseMapElement{ - { - Id: licenseId, - Add: false, - }, - }, - } - - w := makeRequest("PATCH", "/obligation_maps/obligation/"+obligationId.String()+"/license", mapInputRemove, true) - assert.Equal(t, http.StatusOK, w.Code) - }) - - t.Run("patchObligationMapForNonExistingObligation", func(t *testing.T) { - mapInput := models.LicenseMapInput{ - MapInput: []models.LicenseMapElement{ - { - Id: licenseId, - Add: true, - }, - }, - } - - w := makeRequest("PATCH", "/obligation_maps/obligation/"+uuid.New().String()+"/license", mapInput, true) - assert.Equal(t, http.StatusNotFound, w.Code) - }) - - t.Run("patchObligationMapWithInvalidJSON", func(t *testing.T) { - w := makeRequest("PATCH", "/obligation_maps/obligation/"+obligationId.String()+"/license", "invalid", true) - assert.Equal(t, http.StatusBadRequest, w.Code) - }) -} - -func TestUpdateLicenseInObligationMap(t *testing.T) { - dto := models.ObligationCreateDTO{ - Topic: "test-update-obligation-map", - Type: "RIGHT", - Text: "Test obligation text", - Classification: "GREEN", - Comment: ptr("Test comment"), - Active: ptr(true), - TextUpdatable: ptr(false), - Category: ptr("GENERAL"), - } - createW := makeRequest("POST", "/obligations", dto, true) - assert.Equal(t, http.StatusCreated, createW.Code) - - var createRes models.ObligationResponse - if err := json.Unmarshal(createW.Body.Bytes(), &createRes); err != nil { - t.Fatalf("Failed to parse create response: %v", err) - } - if len(createRes.Data) == 0 { - t.Fatal("No obligation returned in create response") - } - obligationId := createRes.Data[0].Id - - licenseW := makeRequest("GET", "/licenses", nil, true) - assert.Equal(t, http.StatusOK, licenseW.Code) - - var licenseRes models.LicenseResponse - if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { - t.Fatalf("Failed to parse license response: %v", err) - } - - if len(licenseRes.Data) == 0 { - t.Skip("No licenses available for testing") - } - licenseId := licenseRes.Data[0].Id - - t.Run("updateLicenseInObligationMap", func(t *testing.T) { - licenseListInput := models.LicenseListInput{ - LicenseIds: []uuid.UUID{licenseId}, - } - - w := makeRequest("PUT", "/obligation_maps/obligation/"+obligationId.String()+"/license", licenseListInput, true) - assert.Equal(t, http.StatusOK, w.Code) - - var res models.ObligationMapResponse - if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { - t.Errorf("Error unmarshalling JSON: %v", err) - return - } - assert.Equal(t, http.StatusOK, res.Status) - }) - - t.Run("updateLicenseInObligationMapWithEmptyList", func(t *testing.T) { - licenseListInput := models.LicenseListInput{ - LicenseIds: []uuid.UUID{}, - } - - w := makeRequest("PUT", "/obligation_maps/obligation/"+obligationId.String()+"/license", licenseListInput, true) - assert.Equal(t, http.StatusOK, w.Code) - }) - - t.Run("updateLicenseInObligationMapForNonExistingObligation", func(t *testing.T) { - licenseListInput := models.LicenseListInput{ - LicenseIds: []uuid.UUID{licenseId}, - } - - w := makeRequest("PUT", "/obligation_maps/obligation/"+uuid.New().String()+"/license", licenseListInput, true) - assert.Equal(t, http.StatusNotFound, w.Code) - }) - - t.Run("updateLicenseInObligationMapWithInvalidJSON", func(t *testing.T) { - w := makeRequest("PUT", "/obligation_maps/obligation/"+obligationId.String()+"/license", "invalid", true) - assert.Equal(t, http.StatusBadRequest, w.Code) - }) -} From 7dfb3defe8469dc5c07befa3cba7c67167045a73 Mon Sep 17 00:00:00 2001 From: Dearsh Oberoi Date: Tue, 24 Feb 2026 13:45:31 +0530 Subject: [PATCH 2/2] refactor(obligations): support linking and unlinking of licenses in obligation endpoints Signed-off-by: Dearsh Oberoi --- pkg/api/obligations.go | 426 +++++++++++++++--------- pkg/models/obligations.go | 4 +- pkg/utils/util.go | 130 +------- tests/obligations_comprehensive_test.go | 134 ++++++++ tests/obligations_test.go | 48 ++- 5 files changed, 455 insertions(+), 287 deletions(-) diff --git a/pkg/api/obligations.go b/pkg/api/obligations.go index f969d90..7dbb712 100644 --- a/pkg/api/obligations.go +++ b/pkg/api/obligations.go @@ -10,23 +10,22 @@ package api import ( "encoding/json" + "errors" "fmt" "net/http" "path/filepath" "reflect" + "slices" "strconv" "strings" "time" - "golang.org/x/exp/slices" - "github.com/fossology/LicenseDb/pkg/db" "github.com/fossology/LicenseDb/pkg/models" "github.com/fossology/LicenseDb/pkg/utils" "github.com/gin-gonic/gin" "github.com/google/uuid" "gorm.io/gorm" - "gorm.io/gorm/clause" ) // GetAllObligation retrieves a list of all obligation records @@ -195,25 +194,11 @@ func CreateObligation(c *gin.Context) { _ = db.DB.Transaction(func(tx *gorm.DB) error { ob := obligation.ConvertToObligation() - result := tx.Omit("Licenses").Create(&ob) - - if result.Error != nil { + if err := tx.Omit("Licenses").Create(&ob).Error; err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, Message: "Failed to create obligation", - Error: result.Error.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - return result.Error - } - - if err := addChangelogsForObligation(tx, userId, &ob, &models.Obligation{}); err != nil { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "Failed to create obligation", - Error: result.Error.Error(), + Error: err.Error(), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } @@ -221,40 +206,61 @@ func CreateObligation(c *gin.Context) { return err } - var combinedMapErrors string - var removeLicenses, insertLicenses []uuid.UUID - insertLicenses = obligation.LicenseIds - _, errs := utils.PerformObligationMapActions(tx, userId, &ob, removeLicenses, insertLicenses) + insertLicenses := obligation.LicenseIds + errs := utils.PerformObligationMapActions(tx, userId, &ob, insertLicenses) if len(errs) != 0 { + var combinedMapErrors strings.Builder for _, err := range errs { if err != nil { - combinedMapErrors += fmt.Sprintf("%s\n", err) + fmt.Fprintf(&combinedMapErrors, "%s\n", err) } } + er := models.LicenseError{ + Status: http.StatusNotFound, + Message: "Failed to create obligation", + Error: combinedMapErrors.String(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + } + c.JSON(http.StatusNotFound, er) + return errors.New(combinedMapErrors.String()) } - if combinedMapErrors == "" { - obdto := ob.ConvertToObligationResponseDTO() - res := models.ObligationResponse{ - Data: []models.ObligationResponseDTO{obdto}, - Status: http.StatusOK, - Meta: models.PaginationMeta{ - ResourceCount: 1, - }, + if err := tx.Joins("Classification").Joins("Type").Preload("Licenses").First(&ob, ob.Id).Error; err != nil { + er := models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to create obligation", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), } + c.JSON(http.StatusInternalServerError, er) + return err + } - c.JSON(http.StatusCreated, res) - } else { + if err := addChangelogsForObligation(tx, userId, &ob, &models.Obligation{}); err != nil { er := models.LicenseError{ Status: http.StatusBadRequest, - Message: "Obligation created successfully but license association failed", - Error: combinedMapErrors, + Message: "Failed to create obligation", + Error: err.Error(), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } c.JSON(http.StatusBadRequest, er) + return err } + obdto := ob.ConvertToObligationResponseDTO() + res := models.ObligationResponse{ + Data: []models.ObligationResponseDTO{obdto}, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, + } + + c.JSON(http.StatusCreated, res) + return nil }) } @@ -278,7 +284,6 @@ func CreateObligation(c *gin.Context) { func UpdateObligation(c *gin.Context) { var updates models.ObligationUpdateDTO var oldObligation models.Obligation - userId := c.MustGet("userId").(uuid.UUID) obligationId, err := uuid.Parse(c.Param("id")) if err != nil { @@ -293,27 +298,27 @@ func UpdateObligation(c *gin.Context) { return } - if err := db.DB.Joins("Classification").Joins("Type").Preload("Licenses").Where(models.Obligation{Id: obligationId}).First(&oldObligation).Error; err != nil { + if err := c.ShouldBindJSON(&updates); err != nil { er := models.LicenseError{ - Status: http.StatusNotFound, - Message: fmt.Sprintf("obligation with id '%s' not found", obligationId.String()), + Status: http.StatusBadRequest, + Message: "invalid json body", Error: err.Error(), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } - c.JSON(http.StatusNotFound, er) + c.JSON(http.StatusBadRequest, er) return } - if err := c.ShouldBindJSON(&updates); err != nil { + if err := db.DB.Joins("Classification").Joins("Type").Preload("Licenses").Where(models.Obligation{Id: obligationId}).First(&oldObligation).Error; err != nil { er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "invalid json body", + Status: http.StatusNotFound, + Message: fmt.Sprintf("obligation with id '%s' not found", obligationId.String()), Error: err.Error(), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } - c.JSON(http.StatusBadRequest, er) + c.JSON(http.StatusNotFound, er) return } @@ -330,7 +335,6 @@ func UpdateObligation(c *gin.Context) { return } newObligation.Id = oldObligation.Id - var combinedMapErrors string if err := db.DB.Transaction(func(tx *gorm.DB) error { // Overwrite values of existing keys, add new key value pairs and remove keys with null values. @@ -342,36 +346,26 @@ func UpdateObligation(c *gin.Context) { return err } - if err := tx.Joins("Type").Joins("Classification").First(&newObligation).Error; err != nil { - return err + userId := c.MustGet("userId").(uuid.UUID) + if updates.LicenseIds != nil { + errs := utils.PerformObligationMapActions(tx, userId, &oldObligation, *updates.LicenseIds) + if len(errs) != 0 { + var combinedMapErrors strings.Builder + for _, err := range errs { + if err != nil { + fmt.Fprintf(&combinedMapErrors, "%s\n", err) + } + } + return errors.New(combinedMapErrors.String()) + } } - if err := addChangelogsForObligation(tx, userId, &newObligation, &oldObligation); err != nil { + if err := tx.Joins("Type").Joins("Classification").Preload("Licenses").First(&newObligation).Error; err != nil { return err } - if updates.LicenseIds == nil { - return nil - } - - if err := tx.Where(&models.Obligation{Id: oldObligation.Id}).Preload("Licenses").First(&oldObligation).Error; err != nil { - return nil - } - var licenseIds, removeLicenses, insertLicenses []uuid.UUID - - licenseIds = slices.Compact(*updates.LicenseIds) - - utils.GenerateDiffForReplacingLicenses(&oldObligation, licenseIds, &removeLicenses, &insertLicenses) - - userId := c.MustGet("userId").(uuid.UUID) - _, errs := utils.PerformObligationMapActions(tx, userId, &oldObligation, removeLicenses, insertLicenses) - if len(errs) != 0 { - for _, err := range errs { - if err != nil { - combinedMapErrors += fmt.Sprintf("%s\n", err) - } - } - return nil + if err := addChangelogsForObligation(tx, userId, &newObligation, &oldObligation); err != nil { + return err } return nil @@ -386,28 +380,16 @@ func UpdateObligation(c *gin.Context) { c.JSON(http.StatusBadRequest, er) return } + obDto := newObligation.ConvertToObligationResponseDTO() - if combinedMapErrors != "" { - er := models.LicenseError{ - Status: http.StatusBadRequest, - Message: "Obligation created successfully but license association failed", - Error: combinedMapErrors, - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - } - c.JSON(http.StatusBadRequest, er) - } else { - obDto := newObligation.ConvertToObligationResponseDTO() - - res := models.ObligationResponse{ - Data: []models.ObligationResponseDTO{obDto}, - Status: http.StatusOK, - Meta: models.PaginationMeta{ - ResourceCount: 1, - }, - } - c.JSON(http.StatusOK, res) + res := models.ObligationResponse{ + Data: []models.ObligationResponseDTO{obDto}, + Status: http.StatusOK, + Meta: models.PaginationMeta{ + ResourceCount: 1, + }, } + c.JSON(http.StatusOK, res) } // DeleteObligation marks an existing obligation record as inactive @@ -602,16 +584,17 @@ func ImportObligations(c *gin.Context) { oldObligation := ob.ConvertToObligation() newObligation := oldObligation _ = db.DB.Transaction(func(tx *gorm.DB) error { - // If id not present in json object, create a new one. + // (a) If id not present in json object, create a new one. // - // If id present in json object, but entry not found in database (can arise in cases when + // (b) If id present in json object, but entry not found in database (can arise in cases when // license/obligation file exported from one LicenseDb instance is imported in another instance) // then create a new one // - // If id present in json object and entry found in database, update the database with field values + // (c) If id present in json object and entry found in database, update the database with field values // of the json object if ob.Id == nil { - result := tx.Create(&oldObligation) + // Case (a) + result := tx.Omit("Licenses").Create(&oldObligation) if result.Error != nil { res.Data = append(res.Data, models.LicenseError{ Status: http.StatusBadRequest, @@ -623,45 +606,34 @@ func ImportObligations(c *gin.Context) { return err } - if err := addChangelogsForObligation(tx, userId, &oldObligation, &models.Obligation{}); err != nil { - res.Data = append(res.Data, models.LicenseError{ - Status: http.StatusInternalServerError, - Message: "Failed to update license", - Error: err.Error(), - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - }) - return err + var message string + if ob.LicenseIds != nil { + userId := c.MustGet("userId").(uuid.UUID) + errs := utils.PerformObligationMapActions(tx, userId, &oldObligation, *ob.LicenseIds) + if len(errs) != 0 { + var combinedMapErrors strings.Builder + for _, err := range errs { + if err != nil { + fmt.Fprintf(&combinedMapErrors, "%s\n", err) + } + } + message = fmt.Sprintf("Obligation created successfully but there was en error creating license associations: %s", combinedMapErrors.String()) + } } - // case when obligation doesn't exist in database and is inserted - res.Data = append(res.Data, models.ObligationImportStatus{ - Data: models.ObligationId{Id: oldObligation.Id, Topic: *oldObligation.Topic}, - Status: http.StatusCreated, - Message: "obligation created successfully", - }) - } else { - if err := tx.Where(&models.Obligation{Id: oldObligation.Id}).First(&oldObligation).Error; err != nil { - res.Data = append(res.Data, models.LicenseError{ + + if err := tx.Joins("Classification").Joins("Type").Preload("Licenses").First(&oldObligation, oldObligation.Id).Error; err != nil { + er := models.LicenseError{ Status: http.StatusInternalServerError, - Message: "Error updating license associations", + Message: "Failed to create obligation", Error: err.Error(), Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), - }) - return nil - } - newObligation.Id = oldObligation.Id - if err := tx.Omit("Licenses", "Topic").Clauses(clause.Returning{}).Updates(&newObligation).Error; err != nil { - res.Data = append(res.Data, models.LicenseError{ - Status: http.StatusBadRequest, - Message: fmt.Sprintf("Failed to update obligation: %s", err.Error()), - Error: *ob.Topic, - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - }) + } + c.JSON(http.StatusInternalServerError, er) return err } - if err := addChangelogsForObligation(tx, userId, &newObligation, &oldObligation); err != nil { + + if err := addChangelogsForObligation(tx, userId, &oldObligation, &models.Obligation{}); err != nil { res.Data = append(res.Data, models.LicenseError{ Status: http.StatusInternalServerError, Message: "Failed to update license", @@ -671,50 +643,162 @@ func ImportObligations(c *gin.Context) { }) return err } - + if message == "" { + message = "obligation created successfully" + } res.Data = append(res.Data, models.ObligationImportStatus{ - Data: models.ObligationId{Id: *ob.Id, Topic: *ob.Topic}, - Status: http.StatusOK, - Message: "obligation updated successfully", + Data: models.ObligationId{Id: oldObligation.Id, Topic: *oldObligation.Topic}, + Status: http.StatusCreated, + Message: message, }) - } + } else { + if err := tx.Where(&models.Obligation{Id: oldObligation.Id}).First(&oldObligation).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + result := tx.Omit("Licenses").Create(&oldObligation) + if result.Error != nil { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusBadRequest, + Message: fmt.Sprintf("Failed to create/update obligation: %s", result.Error.Error()), + Error: *ob.Topic, + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } + + var message string + if ob.LicenseIds != nil { + userId := c.MustGet("userId").(uuid.UUID) + errs := utils.PerformObligationMapActions(tx, userId, &oldObligation, *ob.LicenseIds) + if len(errs) != 0 { + var combinedMapErrors strings.Builder + for _, err := range errs { + if err != nil { + fmt.Fprintf(&combinedMapErrors, "%s\n", err) + } + } + message = fmt.Sprintf("Obligation created successfully but there was en error creating license associations: %s", combinedMapErrors.String()) + } + } + + if err := tx.Joins("Classification").Joins("Type").Preload("Licenses").First(&oldObligation, oldObligation.Id).Error; err != nil { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to update license", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } + + if err := addChangelogsForObligation(tx, userId, &oldObligation, &models.Obligation{}); err != nil { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to update license", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } + if message == "" { + message = "obligation created successfully" + } + res.Data = append(res.Data, models.ObligationImportStatus{ + Data: models.ObligationId{Id: oldObligation.Id, Topic: *oldObligation.Topic}, + Status: http.StatusCreated, + Message: message, + }) + } else { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Error updating license associations", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return nil + } + } else { + if newObligation.Text != nil && *oldObligation.Text != *newObligation.Text && !*oldObligation.TextUpdatable { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusBadRequest, + Message: "Text is not updatable", + Error: "Field `text_updatable` needs to be true to update the text", + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return errors.New("field `text_updatable` needs to be true to update the text") + } + if err := tx.Model(&models.Obligation{}).Where(models.Obligation{Id: oldObligation.Id}).UpdateColumn("external_ref", gorm.Expr("jsonb_strip_nulls(COALESCE(external_ref, '{}'::jsonb) || ?)", ob.ExternalRef)).Error; err != nil { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to update license", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } - if ob.LicenseIds == nil { - return nil - } - // do not return error so that obligations - // are created/updated even if the association fails(license is not found etc) - if err := tx.Where(&models.Obligation{Id: oldObligation.Id}).Preload("Licenses").First(&oldObligation).Error; err != nil { - return nil - } - var licenseIds, removeLicenses, insertLicenses []uuid.UUID + if err := tx.Omit("ExternalRef", "Licenses", "Topic").Updates(&newObligation).Error; err != nil { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to update license", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } + + var message string + if ob.LicenseIds != nil { + userId := c.MustGet("userId").(uuid.UUID) + errs := utils.PerformObligationMapActions(tx, userId, &oldObligation, *ob.LicenseIds) + if len(errs) != 0 { + var combinedMapErrors strings.Builder + for _, err := range errs { + if err != nil { + fmt.Fprintf(&combinedMapErrors, "%s\n", err) + } + } + message = fmt.Sprintf("Obligation updated successfully but there was en error updating license associations: %s", combinedMapErrors.String()) + } + } - licenseIds = slices.Compact(*ob.LicenseIds) + if err := tx.Joins("Type").Joins("Classification").Preload("Licenses").First(&newObligation).Error; err != nil { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to update license", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } - utils.GenerateDiffForReplacingLicenses(&oldObligation, licenseIds, &removeLicenses, &insertLicenses) + if err := addChangelogsForObligation(tx, userId, &newObligation, &oldObligation); err != nil { + res.Data = append(res.Data, models.LicenseError{ + Status: http.StatusInternalServerError, + Message: "Failed to update license", + Error: err.Error(), + Path: c.Request.URL.Path, + Timestamp: time.Now().Format(time.RFC3339), + }) + return err + } - userId := c.MustGet("userId").(uuid.UUID) - _, errs := utils.PerformObligationMapActions(tx, userId, &oldObligation, removeLicenses, insertLicenses) - if len(errs) != 0 { - var combinedErrors string - for _, err := range errs { - if err != nil { - combinedErrors += fmt.Sprintf("%s\n", err) + if message == "" { + message = "obligation updated successfully" } + res.Data = append(res.Data, models.ObligationImportStatus{ + Data: models.ObligationId{Id: *ob.Id, Topic: *ob.Topic}, + Status: http.StatusOK, + Message: message, + }) } - res.Data = append(res.Data, models.LicenseError{ - Status: http.StatusInternalServerError, - Message: "Error updating license associations", - Error: combinedErrors, - Path: c.Request.URL.Path, - Timestamp: time.Now().Format(time.RFC3339), - }) - } else { - res.Data = append(res.Data, models.ObligationImportStatus{ - Data: models.ObligationId{Id: oldObligation.Id, Topic: *ob.Topic}, - Status: http.StatusOK, - Message: "licenses associated with obligations successfully", - }) } return nil @@ -771,6 +855,17 @@ func ExportObligations(c *gin.Context) { // addChangelogsForObligation adds changelogs for the updated fields on obligation update func addChangelogsForObligation(tx *gorm.DB, userId uuid.UUID, newObligation, oldObligation *models.Obligation) error { + uuidsToStr := func(ids []models.LicenseDB) string { + if len(ids) == 0 { + return "" + } + s := make([]string, 0, len(ids)) + for _, lic := range ids { + s = append(s, lic.Id.String()) + } + slices.Sort(s) + return strings.Join(s, ", ") + } var changes []models.ChangeLog var oldType, newType *string @@ -828,6 +923,11 @@ func addChangelogsForObligation(tx *gorm.DB, userId uuid.UUID, } } + oldVal := uuidsToStr(oldObligation.Licenses) + newVal := uuidsToStr(newObligation.Licenses) + + utils.AddChangelog("Licenses", &oldVal, &newVal, &changes) + if len(changes) != 0 { audit := models.Audit{ UserId: userId, diff --git a/pkg/models/obligations.go b/pkg/models/obligations.go index 0ad9ab6..5cb0848 100644 --- a/pkg/models/obligations.go +++ b/pkg/models/obligations.go @@ -305,7 +305,9 @@ type ObligationFileDTO struct { func (obDto *ObligationFileDTO) ConvertToObligation() Obligation { var o Obligation - + if obDto.Id != nil { + o.Id = *obDto.Id + } o.Topic = obDto.Topic if obDto.Type != nil { o.Type = &ObligationType{Type: *obDto.Type} diff --git a/pkg/utils/util.go b/pkg/utils/util.go index f01be78..c17e3fe 100644 --- a/pkg/utils/util.go +++ b/pkg/utils/util.go @@ -216,138 +216,26 @@ func InsertOrUpdateLicenseOnImport(lic *models.LicenseImportDTO, userId uuid.UUI return message, importStatus } -// GenerateDiffForReplacingLicenses creates list of license associations to be created and deleted such that the list of currently associated -// licenses to a obligation is overwritten by the list provided in the param newLicenseAssociations -func GenerateDiffForReplacingLicenses(obligation *models.Obligation, newLicenseAssociations []uuid.UUID, removeLicenses, insertLicenses *[]uuid.UUID) { - // if license in currently associated with the obligation but isn't in newLicenseAssociations, remove it - for _, lic := range obligation.Licenses { - found := false - for _, id := range newLicenseAssociations { - if id == lic.Id { - found = true - break - } - } - if !found { - *removeLicenses = append(*removeLicenses, lic.Id) - } - } - - // if license in newLicenseAssociations but not currently associated with the obligation, insert it - for _, id := range newLicenseAssociations { - found := false - for _, lic := range obligation.Licenses { - if id == lic.Id { - found = true - break - } - } - if !found { - *insertLicenses = append(*insertLicenses, id) - } - } -} - -// PerformObligationMapActions created associations for licenses in insertLicenses and deletes -// associations for licenses in removeLicenses. It also calculates changelog for the changes. -// It returns the final list of associated licenses. -func PerformObligationMapActions(tx *gorm.DB, userId uuid.UUID, obligation *models.Obligation, - removeLicenses, insertLicenses []uuid.UUID) ([]models.ObligationMapLicenseFormat, []error) { - createLicenseAssociations := []models.LicenseDB{} - deleteLicenseAssociations := []models.LicenseDB{} - var oldLicenseAssociations, newLicenseAssociations []models.ObligationMapLicenseFormat +// PerformObligationMapActions replaces current associated licenses with the list of licenses whose ids are provided in the newLicenseIds. +func PerformObligationMapActions(tx *gorm.DB, userId uuid.UUID, obligation *models.Obligation, newLicenseIds []uuid.UUID) []error { + newLicenseAssociations := []models.LicenseDB{} var errs []error - for _, lic := range obligation.Licenses { - oldLicenseAssociations = append(oldLicenseAssociations, models.ObligationMapLicenseFormat{Id: lic.Id, Shortname: *lic.Shortname}) - } - - for _, licId := range insertLicenses { + for _, licId := range newLicenseIds { var license models.LicenseDB - if err := tx.Where(models.LicenseDB{Id: licId}).First(&license).Error; err != nil { + activeStatus := true + if err := tx.Where(models.LicenseDB{Id: licId, Active: &activeStatus}).First(&license).Error; err != nil { errs = append(errs, fmt.Errorf("unable to associate license '%s' to obligation '%s': %s", licId, obligation.Id, err.Error())) } else { - createLicenseAssociations = append(createLicenseAssociations, license) + newLicenseAssociations = append(newLicenseAssociations, license) } } - for _, licId := range removeLicenses { - var license models.LicenseDB - if err := tx.Where(models.LicenseDB{Id: licId}).First(&license).Error; err != nil { - errs = append(errs, fmt.Errorf("unable to remove license '%s' from obligation '%s': %s", licId, obligation.Id, err.Error())) - } else { - deleteLicenseAssociations = append(deleteLicenseAssociations, license) - } - } - - if err := tx.Transaction(func(tx1 *gorm.DB) error { - - if len(createLicenseAssociations) != 0 || len(deleteLicenseAssociations) != 0 { - if err := tx1.Model(obligation).Association("Licenses").Append(createLicenseAssociations); err != nil { - return err - } - if err := tx1.Model(obligation).Association("Licenses").Delete(deleteLicenseAssociations); err != nil { - return err - } - } - - if err := tx. - Preload("Licenses"). - Joins("Type"). - Joins("Classification"). - Where(&models.Obligation{Id: obligation.Id}). - First(&obligation).Error; err != nil { - return err - } - - for _, lic := range obligation.Licenses { - newLicenseAssociations = append(newLicenseAssociations, models.ObligationMapLicenseFormat{Id: lic.Id, Shortname: *lic.Shortname}) - } - - return createObligationMapChangelog(tx, userId, newLicenseAssociations, oldLicenseAssociations, obligation) - }); err != nil { + if err := tx.Model(obligation).Association("Licenses").Replace(newLicenseAssociations); err != nil { errs = append(errs, err) } - return newLicenseAssociations, errs -} - -// createObligationMapChangelog creates the changelog for the obligation map changes. -func createObligationMapChangelog( - tx *gorm.DB, - userId uuid.UUID, - newLicenseAssociations, oldLicenseAssociations []models.ObligationMapLicenseFormat, - obligation *models.Obligation, -) error { - uuidsToStr := func(ids []models.ObligationMapLicenseFormat) string { - if len(ids) == 0 { - return "" - } - s := make([]string, 0, len(ids)) - for _, lic := range ids { - s = append(s, lic.Id.String()) - } - return strings.Join(s, ", ") - } - - oldVal := uuidsToStr(oldLicenseAssociations) - newVal := uuidsToStr(newLicenseAssociations) - var changes []models.ChangeLog - AddChangelog("Licenses", &oldVal, &newVal, &changes) - - audit := models.Audit{ - UserId: userId, - TypeId: obligation.Id, - Timestamp: time.Now(), - Type: "OBLIGATION", - ChangeLogs: changes, - } - - if err := tx.Create(&audit).Error; err != nil { - return err - } - - return nil + return errs } func AddChangelogForObligationType(tx *gorm.DB, userId uuid.UUID, oldObType, newObType *models.ObligationType) error { diff --git a/tests/obligations_comprehensive_test.go b/tests/obligations_comprehensive_test.go index 5732f5a..f029380 100644 --- a/tests/obligations_comprehensive_test.go +++ b/tests/obligations_comprehensive_test.go @@ -293,6 +293,140 @@ func TestImportObligations(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) }) + + t.Run("importSuccessUpdateExistingLicense", func(t *testing.T) { + licenseW := makeRequest("GET", "/licenses", nil, true) + assert.Equal(t, http.StatusOK, licenseW.Code) + + var licenseRes models.LicenseResponse + if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { + t.Fatalf("Failed to parse license response: %v", err) + } + + var licenseId *uuid.UUID + if len(licenseRes.Data) > 0 { + licenseId = &licenseRes.Data[0].Id + } + + obligations := []models.ObligationFileDTO{ + { + Id: ptr(uuid.New()), + Topic: ptr("IMPORT-OBLIGATION-1"), + Type: ptr("RIGHT"), + Text: ptr("Test obligation text for import"), + Classification: ptr("GREEN"), + Comment: ptr("Test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + }, + } + + if licenseId != nil { + obligations[0].LicenseIds = &[]uuid.UUID{*licenseId} + } + + jsonData, err := json.Marshal(obligations) + assert.NoError(t, err) + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", "obligations.json") + assert.NoError(t, err) + _, err = part.Write(jsonData) + assert.NoError(t, err) + writer.Close() + + fullPath := baseURL + "/obligations/import" + req := httptest.NewRequest("POST", fullPath, body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", "Bearer "+AuthToken) + w := httptest.NewRecorder() + api.Router().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ImportObligationsResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("importSuccessUpdateExistingObligation", func(t *testing.T) { + licenseW := makeRequest("GET", "/licenses", nil, true) + assert.Equal(t, http.StatusOK, licenseW.Code) + + var licenseRes models.LicenseResponse + if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { + t.Fatalf("Failed to parse license response: %v", err) + } + + var licenseId *uuid.UUID + if len(licenseRes.Data) > 0 { + licenseId = &licenseRes.Data[0].Id + } + + dto := models.ObligationCreateDTO{ + Topic: "test-import-topic", + Type: "RIGHT", + Text: "test text for import", + Classification: "GREEN", + Comment: ptr("unit test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + ExternalRef: models.ObligationSchemaExtension{ + ObligationExplanation: ptr("this is a test explaination to test the external ref functionality"), + }, + LicenseIds: []uuid.UUID{*licenseId}, + } + id := assertObligationCreated(t, dto) + + licenseId = &licenseRes.Data[1].Id + obligations := []models.ObligationFileDTO{ + { + Id: ptr(id), + Topic: ptr("IMPORT-OBLIGATION-UPDATE"), + Type: ptr("RIGHT"), + Text: ptr("Test obligation text for import"), + Classification: ptr("GREEN"), + Comment: ptr("Test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + LicenseIds: &[]uuid.UUID{*licenseId}, + }, + } + + jsonData, err := json.Marshal(obligations) + assert.NoError(t, err) + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", "obligations.json") + assert.NoError(t, err) + _, err = part.Write(jsonData) + assert.NoError(t, err) + writer.Close() + + fullPath := baseURL + "/obligations/import" + req := httptest.NewRequest("POST", fullPath, body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", "Bearer "+AuthToken) + w := httptest.NewRecorder() + api.Router().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ImportObligationsResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) } func TestGetSimilarObligations(t *testing.T) { diff --git a/tests/obligations_test.go b/tests/obligations_test.go index eaf654b..07a0285 100644 --- a/tests/obligations_test.go +++ b/tests/obligations_test.go @@ -76,7 +76,7 @@ func TestCreateObligation(t *testing.T) { } w := makeRequest("POST", "/obligations", dto, true) - assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) var res models.LicenseError if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { @@ -84,7 +84,7 @@ func TestCreateObligation(t *testing.T) { return } - expectedError := "Obligation created successfully but license association failed" + expectedError := "Failed to create obligation" assert.Equal(t, expectedError, res.Message) }) @@ -199,6 +199,50 @@ func TestUpdateObligation(t *testing.T) { assert.Equal(t, http.StatusNotFound, res.Status) }) + t.Run("UpdateLicenseAssociations", func(t *testing.T) { + w := makeRequest("GET", "/licenses", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + + updateDTO := models.ObligationUpdateDTO{ + LicenseIds: ptr([]uuid.UUID{res.Data[0].Id, res.Data[1].Id}), + } + + assertObligationUpdated(t, id, updateDTO) + + updateDTO = models.ObligationUpdateDTO{ + LicenseIds: ptr([]uuid.UUID{res.Data[2].Id}), + } + + assertObligationUpdated(t, id, updateDTO) + + updateDTO = models.ObligationUpdateDTO{ + LicenseIds: ptr([]uuid.UUID{}), + } + + assertObligationUpdated(t, id, updateDTO) + }) + + t.Run("UpdateLicenseAssociationsWithNonExistentLicense", func(t *testing.T) { + updateDTO := models.ObligationUpdateDTO{ + LicenseIds: ptr([]uuid.UUID{uuid.New()}), + } + + w := makeRequest("PATCH", "/obligations/"+id.String(), updateDTO, true) + + var res models.LicenseError + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + + assert.Equal(t, http.StatusBadRequest, res.Status) + }) } func TestDeleteObligation(t *testing.T) {