Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
AmirAgassi committed Dec 27, 2024
1 parent 338e434 commit 0e26ca6
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 4 deletions.
49 changes: 48 additions & 1 deletion backend/internal/tests/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ import (
"github.com/stretchr/testify/assert"
)

/*
* TestProjectEndpoints tests the complete project lifecycle and error cases
* for the project-related API endpoints. It covers:
* - Project creation
* - Project listing
* - Project retrieval
* - Project submission including answering questions
* - Error handling for various invalid scenarios
*
* The test creates a verified user and company first, then runs
* through the project workflows using that test data.
*/
func TestProjectEndpoints(t *testing.T) {
// Setup test environment
setupEnv()
Expand Down Expand Up @@ -86,6 +98,12 @@ func TestProjectEndpoints(t *testing.T) {
var projectID string

t.Run("Create Project", func(t *testing.T) {
/*
* "Create Project" test verifies:
* - Project creation with valid data
* - Response contains valid project ID
* - Project is associated with correct company
*/
projectBody := fmt.Sprintf(`{
"company_id": "%s",
"title": "Test Project",
Expand Down Expand Up @@ -115,6 +133,11 @@ func TestProjectEndpoints(t *testing.T) {
})

t.Run("List Projects", func(t *testing.T) {
/*
* "List Projects" test verifies:
* - Endpoint returns 200 OK
* - User can see their projects
*/
req := httptest.NewRequest(http.MethodGet, "/api/v1/project", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
rec := httptest.NewRecorder()
Expand All @@ -124,6 +147,11 @@ func TestProjectEndpoints(t *testing.T) {
})

t.Run("Get Project", func(t *testing.T) {
/*
* "Get Project" test verifies:
* - Single project retrieval works
* - Project details are accessible
*/
path := fmt.Sprintf("/api/v1/project/%s", projectID)
t.Logf("Getting project at path: %s", path)

Expand All @@ -138,6 +166,16 @@ func TestProjectEndpoints(t *testing.T) {
})

t.Run("Submit Project", func(t *testing.T) {
/*
* "Submit Project" test verifies the complete submission flow:
* 1. Fetches questions/answers for the project
* 2. Updates each answer with valid data:
* - Website URL for company website
* - Detailed product description (>100 chars)
* - Value proposition description
* 3. Submits the completed project
* 4. Verifies project status changes to 'pending'
*/
// First get the questions/answers
path := fmt.Sprintf("/api/v1/project/%s/answers", projectID)
req := httptest.NewRequest(http.MethodGet, path, nil)
Expand Down Expand Up @@ -217,7 +255,16 @@ func TestProjectEndpoints(t *testing.T) {
assert.Equal(t, "pending", submitResp.Status)
})

// Error cases
/*
* "Error Cases" test suite verifies proper error handling:
* - Invalid project ID returns 404
* - Unauthorized access returns 401
* - Short answers fail validation
* - Invalid URL format fails validation
*
* Uses real question/answer IDs from the project to ensure
* accurate validation testing.
*/
t.Run("Error Cases", func(t *testing.T) {
// First get the questions/answers to get real IDs
path := fmt.Sprintf("/api/v1/project/%s/answers", projectID)
Expand Down
108 changes: 105 additions & 3 deletions backend/internal/v1/v1_projects/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@ import (
"net/http"
)

/*
* Package v1_projects implements the project management endpoints for the SPUR API.
* It handles project creation, retrieval, document management, and submission workflows.
*/

/*
* ValidationError represents a validation failure for a project question.
* Used when validating project submissions and answers.
*/
type ValidationError struct {
Question string `json:"question"`
Message string `json:"message"`
Question string `json:"question"` // The question that failed validation
Message string `json:"message"` // Validation error message
}

func (h *Handler) handleCreateProject(c echo.Context) error {
Expand Down Expand Up @@ -87,6 +96,15 @@ func (h *Handler) handleCreateProject(c echo.Context) error {
})
}

/*
* handleGetProjects retrieves all projects for a company.
*
* Security:
* - Requires authenticated user
* - Only returns projects for user's company
*
* Returns array of ProjectResponse with basic project details
*/
func (h *Handler) handleGetProjects(c echo.Context) error {
// Get user ID from context
userID, err := v1_common.GetUserID(c)
Expand Down Expand Up @@ -127,6 +145,13 @@ func (h *Handler) handleGetProjects(c echo.Context) error {
return c.JSON(200, response)
}

/*
* handleGetProject retrieves a single project by ID.
*
* Security:
* - Verifies project belongs to user's company
* - Returns 404 if project not found or unauthorized
*/
func (h *Handler) handleGetProject(c echo.Context) error {
// Get user ID from context
userID, err := v1_common.GetUserID(c)
Expand Down Expand Up @@ -171,7 +196,16 @@ func (h *Handler) handleGetProject(c echo.Context) error {
})
}


/*
* handlePatchProjectAnswer updates an answer for a project question.
*
* Validation:
* - Validates answer content against question rules
* - Returns validation errors if content invalid
*
* Security:
* - Verifies project belongs to user's company
*/
func (h *Handler) handlePatchProjectAnswer(c echo.Context) error {
// Get project ID from URL
projectID := c.Param("id")
Expand Down Expand Up @@ -237,6 +271,17 @@ func (h *Handler) handlePatchProjectAnswer(c echo.Context) error {
})
}

/*
* handleGetProjectAnswers retrieves all answers for a project.
*
* Returns:
* - Question ID and content
* - Current answer text
* - Question section
*
* Security:
* - Verifies project belongs to user's company
*/
func (h *Handler) handleGetProjectAnswers(c echo.Context) error {
// Get project ID from URL
projectID := c.Param("id")
Expand Down Expand Up @@ -288,6 +333,19 @@ func (h *Handler) handleGetProjectAnswers(c echo.Context) error {
})
}

/*
* handleUploadProjectDocument handles file uploads for a project.
*
* Flow:
* 1. Validates file presence
* 2. Verifies project ownership
* 3. Uploads file to S3
* 4. Creates document record in database
* 5. Returns document details
*
* Cleanup:
* - Deletes S3 file if database insert fails
*/
func (h *Handler) handleUploadProjectDocument(c echo.Context) error {
// Get file from request
file, err := c.FormFile("file")
Expand Down Expand Up @@ -367,6 +425,17 @@ func (h *Handler) handleUploadProjectDocument(c echo.Context) error {
})
}

/*
* handleGetProjectDocuments retrieves all documents for a project.
*
* Returns:
* - Document ID, name, URL
* - Section assignment
* - Creation/update timestamps
*
* Security:
* - Verifies project belongs to user's company
*/
func (h *Handler) handleGetProjectDocuments(c echo.Context) error {
// Get user ID from context
userID, err := v1_common.GetUserID(c)
Expand Down Expand Up @@ -419,6 +488,17 @@ func (h *Handler) handleGetProjectDocuments(c echo.Context) error {
})
}

/*
* handleDeleteProjectDocument removes a document from a project.
*
* Flow:
* 1. Verifies document ownership
* 2. Deletes file from S3
* 3. Removes database record
*
* Security:
* - Verifies document belongs to user's project
*/
func (h *Handler) handleDeleteProjectDocument(c echo.Context) error {
// Get user ID from context
userID, err := v1_common.GetUserID(c)
Expand Down Expand Up @@ -481,6 +561,14 @@ func (h *Handler) handleDeleteProjectDocument(c echo.Context) error {
})
}

/*
* handleListCompanyProjects lists all projects for a company.
* Similar to handleGetProjects but with different response format.
*
* Returns:
* - Array of projects under "projects" key
* - Basic project details including status
*/
func (h *Handler) handleListCompanyProjects(c echo.Context) error {
userID, err := v1_common.GetUserID(c)
if err != nil {
Expand Down Expand Up @@ -522,6 +610,20 @@ func (h *Handler) handleListCompanyProjects(c echo.Context) error {
})
}

/*
* handleSubmitProject handles project submission for review.
*
* Validation:
* 1. Verifies all required questions answered
* 2. Validates all answers against rules
* 3. Returns validation errors if any fail
*
* Flow:
* 1. Collects all project answers
* 2. Validates against question rules
* 3. Updates project status to 'pending'
* 4. Returns success with new status
*/
func (h *Handler) handleSubmitProject(c echo.Context) error {
// Get user ID and verify ownership first
userID, err := v1_common.GetUserID(c)
Expand Down
56 changes: 56 additions & 0 deletions backend/internal/v1/v1_projects/validation.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
package v1_projects

/*
* Package v1_projects provides validation utilities for project answers.
* This file implements the validation rules and message formatting
* for project question answers.
*/

import (
"net/url"
"strings"
"strconv"
"regexp"
)

/*
* validationType defines a single validation rule.
* Each validation has:
* - Name: Rule identifier (e.g., "url", "email")
* - Validate: Function to check if answer meets rule
* - Message: Human-readable error message
*/
type validationType struct {
Name string
Validate func(string, string) bool // (answer, param)
Message string
}

/*
* validationTypes defines all available validation rules.
* Each rule implements specific validation logic:
*
* url: Validates URL format using url.ParseRequestURI
* email: Checks for @ and . characters
* phone: Verifies at least 10 numeric digits
* min: Enforces minimum string length
* max: Enforces maximum string length
* regex: Matches against custom pattern
*/
var validationTypes = []validationType{
{
Name: "url",
Expand Down Expand Up @@ -77,6 +101,14 @@ var validationTypes = []validationType{
},
}

/*
* parseValidationRule splits a validation rule string into name and parameter.
*
* Examples:
* - "min=100" returns ("min", "100")
* - "url" returns ("url", "")
* - "regex=^[0-9]+$" returns ("regex", "^[0-9]+$")
*/
func parseValidationRule(rule string) (name string, param string) {
parts := strings.SplitN(rule, "=", 2)
name = strings.TrimSpace(parts[0])
Expand All @@ -86,6 +118,17 @@ func parseValidationRule(rule string) (name string, param string) {
return
}

/*
* isValidAnswer checks if an answer meets all validation rules.
*
* Parameters:
* - answer: The user's answer text
* - validations: Comma-separated list of rules (e.g., "min=100,url")
*
* Returns:
* - true if answer passes all validations
* - false if any validation fails
*/
func isValidAnswer(answer string, validations string) bool {
rules := strings.Split(validations, ",")

Expand All @@ -101,6 +144,19 @@ func isValidAnswer(answer string, validations string) bool {
return true
}

/*
* getValidationMessage returns human-readable error for failed validation.
*
* Parameters:
* - validations: Comma-separated list of rules
*
* Returns:
* - Formatted error message with parameters substituted
* - Generic "Invalid input" if validation type not found
*
* Example:
* For "min=100", returns "Must be at least 100 characters long"
*/
func getValidationMessage(validations string) string {
rules := strings.Split(validations, ",")

Expand Down

0 comments on commit 0e26ca6

Please sign in to comment.