Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add asset package #48

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

add asset package #48

wants to merge 13 commits into from

Conversation

shumon84
Copy link

@shumon84 shumon84 commented Sep 8, 2024

https://www.notion.so/eukarya/08-Asset-Management-Microservice-2f36dd6b8d534a768776f60fc912f879

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive asset management framework, including asset types, inputs, and mutations in the GraphQL API.
    • Added support for managing assets, asset files, and related functionalities.
    • Implemented a builder pattern for constructing asset and webhook objects.
    • Added a new publication type for project management with various scopes.
    • Introduced asynchronous extraction of ZIP files with progress tracking.
  • Bug Fixes

    • Enhanced error handling across various components, ensuring robustness in asset and project management.
  • Tests

    • Added extensive unit tests for new features, ensuring functionality and reliability across asset, integration, and project management components.
    • Implemented tests for asynchronous unzip functionality to validate performance and error handling.

@shumon84 shumon84 requested a review from rot1024 as a code owner September 8, 2024 17:43
Copy link

coderabbitai bot commented Sep 8, 2024

Walkthrough

This pull request introduces a comprehensive set of domain and infrastructure components for managing assets in a software system. The changes span multiple packages within the asset domain, introducing structs, methods, and interfaces for handling assets, files, projects, integrations, threads, and events. The implementation includes robust error handling, type-safe identifiers, and builder patterns for creating complex objects. Key additions include GraphQL schema definitions, in-memory repositories, file system interactions, and extensive unit testing to ensure the reliability of the new components.

Changes

File Change Summary
.gitignore Added .idea directory to ignored files
asset/asset.graphql New GraphQL schema for asset management, including types, inputs, mutations, and queries
asset/assetdomain/asset/* Added comprehensive asset domain structs and methods for managing assets, files, preview types, and statuses
asset/assetdomain/event/* Introduced event domain with generic event handling and builder
asset/assetdomain/integration/* Added integration domain with webhook and integration management
asset/assetdomain/project/* Implemented project domain with publication and project management
asset/assetdomain/thread/* Created thread and comment management domain
asset/assetinfrastructure/assetfs/* Added file system implementation for asset storage and retrieval
asset/assetinfrastructure/assetmemory/* Introduced in-memory repositories for various asset-related components

Poem

🐰 Hop, hop, through the code's domain,
Assets dancing, no two the same!
Builders craft, with methods neat,
GraphQL schemas make it sweet 🚀
A rabbit's tale of software might! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 57

🧹 Nitpick comments (114)
asset/assetdomain/integration/integration_type_test.go (1)

10-30: Consider adding more test cases for edge scenarios.

To improve test coverage, consider adding test cases for:

  • Empty string input
  • Input with special characters
  • Case sensitivity verification (e.g., "PUBLIC", "Private")

Here's a suggested addition to the test cases:

 tests := []struct {
     name  string
     input string
     want  Type
 }{
     // ... existing test cases ...
+    {
+        name:  "empty string",
+        input: "",
+        want:  TypePrivate,
+    },
+    {
+        name:  "uppercase",
+        input: "PUBLIC",
+        want:  TypePrivate, // or TypePublic depending on intended behavior
+    },
+    {
+        name:  "special characters",
+        input: "public!@#",
+        want:  TypePrivate,
+    },
 }
asset/assetdomain/project/publication_test.go (2)

9-26: Consider using table-driven tests for better maintainability.

While the test cases are comprehensive, converting to a table-driven test would make it easier to add new test cases and improve readability.

Here's a suggested refactoring:

 func TestNewPublication(t *testing.T) {
-	assert.Equal(t, &Publication{
-		scope:       PublicationScopePrivate,
-		assetPublic: false,
-	}, NewPublication(PublicationScopePrivate, false))
-	assert.Equal(t, &Publication{
-		scope:       PublicationScopeLimited,
-		assetPublic: true,
-	}, NewPublication(PublicationScopeLimited, true))
-	assert.Equal(t, &Publication{
-		scope:       PublicationScopePublic,
-		assetPublic: false,
-	}, NewPublication(PublicationScopePublic, false))
-	assert.Equal(t, &Publication{
-		scope:       PublicationScopePrivate,
-		assetPublic: true,
-	}, NewPublication("", true))
+	tests := []struct {
+		name        string
+		scope       string
+		assetPublic bool
+		want        *Publication
+	}{
+		{
+			name:        "private scope with private assets",
+			scope:       PublicationScopePrivate,
+			assetPublic: false,
+			want:        &Publication{scope: PublicationScopePrivate, assetPublic: false},
+		},
+		{
+			name:        "limited scope with public assets",
+			scope:       PublicationScopeLimited,
+			assetPublic: true,
+			want:        &Publication{scope: PublicationScopeLimited, assetPublic: true},
+		},
+		{
+			name:        "public scope with private assets",
+			scope:       PublicationScopePublic,
+			assetPublic: false,
+			want:        &Publication{scope: PublicationScopePublic, assetPublic: false},
+		},
+		{
+			name:        "empty scope defaults to private with public assets",
+			scope:       "",
+			assetPublic: true,
+			want:        &Publication{scope: PublicationScopePrivate, assetPublic: true},
+		},
+	}
+	
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := NewPublication(tt.scope, tt.assetPublic)
+			assert.Equal(t, tt.want, got)
+		})
+	}
}

28-31: Consider adding test cases for all scope values.

While the test covers the default and public scopes, it would be beneficial to test the limited scope as well for complete coverage.

Add this test case:

 func TestPublication_Scope(t *testing.T) {
 	assert.Equal(t, PublicationScopePrivate, (&Publication{}).Scope())
 	assert.Equal(t, PublicationScopePublic, (&Publication{scope: PublicationScopePublic}).Scope())
+	assert.Equal(t, PublicationScopeLimited, (&Publication{scope: PublicationScopeLimited}).Scope())
 }
asset/assetdomain/thread/thread_test.go (3)

12-26: Improve test readability with better variable naming

Consider renaming the got variable to thread to better represent what it is, and make the test setup more explicit.

-	got := Thread{
+	thread := Thread{
 		id:        thid,
 		workspace: wid,
 		comments:  c,
 	}

-	assert.Equal(t, thid, got.ID())
-	assert.Equal(t, wid, got.Workspace())
-	assert.Equal(t, c, got.Comments())
+	assert.Equal(t, thid, thread.ID())
+	assert.Equal(t, wid, thread.Workspace())
+	assert.Equal(t, c, thread.Comments())

39-72: Consider using table-driven tests for better coverage

The current tests cover basic scenarios, but table-driven tests would make it easier to test more edge cases and maintain the tests.

Example refactor for HasComment:

func TestThread_HasComment(t *testing.T) {
    tests := []struct {
        name    string
        thread  *Thread
        comment *Comment
        want    bool
    }{
        {
            name:    "nil thread",
            thread:  nil,
            comment: NewComment(NewCommentID(), operator.OperatorFromUser(NewUserID()), "test"),
            want:    false,
        },
        // Add more test cases
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := tt.thread.HasComment(tt.comment.id)
            assert.Equal(t, tt.want, got)
        })
    }
}

1-137: Consider adding benchmarks and documentation

The test suite is comprehensive but could be enhanced with:

  1. Benchmark tests for performance-critical operations
  2. Doc comments explaining the purpose of each test function
  3. Test coverage for concurrent operations if the Thread type is meant to be thread-safe

Example benchmark:

func BenchmarkThread_AddComment(b *testing.B) {
    thread := &Thread{
        id:        NewID(),
        workspace: accountdomain.NewWorkspaceID(),
    }
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        comment := NewComment(NewCommentID(), operator.OperatorFromUser(NewUserID()), "test")
        _ = thread.AddComment(comment)
    }
}
asset/assetinfrastructure/assetmemory/event_test.go (2)

18-42: Consider enhancing test coverage and maintainability.

While the current test cases cover basic functionality, consider the following improvements:

  1. Extract test data setup into helper functions to reduce duplication with TestEvent_Save
  2. Add test cases for edge cases:
    • nil context
    • nil event ID
  3. Convert to table-driven tests for better maintainability

Here's a suggested refactor:

+func setupTestData(t *testing.T) (*user.User, *asset.Asset, *event.Event[any]) {
+    now := time.Now()
+    u := user.New().NewID().Email("[email protected]").Name("John").MustBuild()
+    a := asset.New().NewID().Project(project.NewID()).Size(100).NewUUID().
+        CreatedByUser(u.ID()).Thread(id.NewThreadID()).MustBuild()
+    ev := event.New[any]().ID(event.NewID()).Timestamp(now).Type(event.AssetCreate).
+        Operator(operator.OperatorFromUser(u.ID())).Object(a).MustBuild()
+    return u, a, ev
+}

 func TestEvent_FindByID(t *testing.T) {
-    now := time.Now()
-    u := user.New().NewID().Email("[email protected]").Name("John").MustBuild()
-    a := asset.New().NewID().Project(project.NewID()).Size(100).NewUUID().
-        CreatedByUser(u.ID()).Thread(id.NewThreadID()).MustBuild()
-    eID1 := event.NewID()
-    ev := event.New[any]().ID(eID1).Timestamp(now).Type(event.AssetCreate).
-        Operator(operator.OperatorFromUser(u.ID())).Object(a).MustBuild()
+    tests := []struct {
+        name    string
+        ctx     context.Context
+        eventID event.ID
+        setup   func(*Event)
+        want    *event.Event[any]
+        wantErr error
+    }{
+        {
+            name:    "success",
+            ctx:     context.Background(),
+            setup:   func(r *Event) {
+                _, _, ev := setupTestData(t)
+                r.Save(context.Background(), ev)
+            },
+            want:    ev,
+            wantErr: nil,
+        },
+        {
+            name:    "not found",
+            ctx:     context.Background(),
+            eventID: event.NewID(),
+            setup:   func(r *Event) {},
+            want:    nil,
+            wantErr: rerror.ErrNotFound,
+        },
+        {
+            name:    "nil context",
+            ctx:     nil,
+            eventID: event.NewID(),
+            setup:   func(r *Event) {},
+            want:    nil,
+            wantErr: rerror.ErrInvalidContext,
+        },
+    }

+    for _, tt := range tests {
+        t.Run(tt.name, func(t *testing.T) {
+            r := NewEvent()
+            tt.setup(r)
+            got, err := r.FindByID(tt.ctx, tt.eventID)
+            assert.Equal(t, tt.wantErr, err)
+            assert.Equal(t, tt.want, got)
+        })
+    }
 }

1-61: Consider implementing a test helper package.

Given the complexity of creating test data (users, assets, events) and the likelihood of needing these helpers across multiple test files, consider creating a dedicated test helper package. This would:

  1. Reduce duplication across test files
  2. Ensure consistent test data creation
  3. Make tests more maintainable
  4. Provide reusable assertions for common checks

Consider creating assetmemory/testing/factory.go with helper functions:

package testing

// Factory provides methods to create test entities
type Factory struct {
    t *testing.T
}

func NewFactory(t *testing.T) *Factory {
    return &Factory{t: t}
}

func (f *Factory) NewUser() *user.User {
    return user.New().NewID().Email("[email protected]").Name("Test User").MustBuild()
}

func (f *Factory) NewAsset(u *user.User) *asset.Asset {
    return asset.New().NewID().Project(project.NewID()).Size(100).NewUUID().
        CreatedByUser(u.ID()).Thread(id.NewThreadID()).MustBuild()
}

func (f *Factory) NewEvent(u *user.User, a *asset.Asset) *event.Event[any] {
    return event.New[any]().ID(event.NewID()).Timestamp(time.Now()).
        Type(event.AssetCreate).Operator(operator.OperatorFromUser(u.ID())).
        Object(a).MustBuild()
}
asset/assetdomain/file/file.go (3)

17-22: Consider a size check initialization.
Currently, the File struct doesn't compute or store file size except when created from URL. If there's a potential need to handle size constraints (e.g., max file size limits on uploads), consider adding logic to get the size from the multipart part to unify size handling.


56-69: Handle redirect or advanced HTTP statuses.
The code currently does a simple GET and checks if StatusCode > 300. It might be worth following redirects or differentiating 4xx vs. 5xx errors for more targeted error messages. This can clarify errors for users (e.g., “404 Not Found” vs “500 Internal Server Error”), rather than a generic error.


74-79: Refine file name inference.
When inferring the file name from the Content-Disposition header, consider fallback strategies for unknown or maliciously crafted headers.

asset/assetdomain/thread/thread.go (2)

51-58: Prevent in-place content overwrite.
When updating a comment, content is overwritten without versioning or undo support. If audit trails are required, consider storing older revisions or timestamps in order to track changes historically.


60-69: Optimize the delete operation.
You call th.Comments() multiple times, resulting in repeated array clones. Although not a big performance issue for small collections, repeated clones may add overhead. Consider storing the index once and operating directly on th.comments.

asset/assetinfrastructure/assetmemory/asset.go (3)

17-21: Consider lock granularity.
All assets are stored in a SyncMap, which is good for concurrency. However, if frequent writes or large sets of data are expected, you might explore finer-grained locking or a sharded approach to avoid potential bottlenecks.


57-83: Check pagination queries with filters.
While the code returns everything in one pass before constructing pagination info, for very large result sets, consider partial retrieval or chunking. This could be beneficial if you plan to scale.


86-97: Log persist events.
When saving an asset, it might be helpful to log the event, including the ID or relevant project info, to assist in debugging or auditing.

asset/assetdomain/asset/preview_type.go (2)

73-81: Log or handle unknown content types.
When a file content type is unrecognized, the code simply returns PreviewTypeUnknown. You might want to log or track these instances for debugging, especially if you expect certain content types that might occasionally be missed.


83-94: Extend recognized MIME types.
“text/csv” is recognized, but other plain text variants or CSV-like types (e.g., “text/tab-separated-values”) might exist. Consider expanding coverage or letting users map custom content types.

asset/assetdomain/project/project.go (2)

16-19: Validate alias string length more precisely.
While the current regular expression enforces the length range (5-32), consider documenting the rationale for these limits (5 and 32) directly in the code or a comment for better maintainability.


108-115: Ensure alias validation errors are handled consistently.
Currently, a simple boolean is used to validate the alias, returning an error if the pattern check fails. You might consider logging or providing more context in the error message to help users identify the invalid alias reason.

asset/asset.graphql (1)

3-16: Redundancy of both 'project' and 'projectId' fields.
Including both fields may be redundant. Consider if the 'project' object can be resolved via 'projectId' to reduce potential schema clutter.

asset/assetdomain/asset/file.go (2)

105-117: Potential infinite recursion.
While FlattenChildren() recursively collates children, ensure there's no circular reference scenario, or it could cause infinite recursion. If this is guaranteed not to happen by design, consider adding a protective check or an explicit comment stating the assumption.


126-164: Memory usage caution in FoldFiles.
For large file sets, the approach of sorting and building a multi-level directory structure in memory might consume significant resources. If the expected usage is large, consider streaming or chunked approaches to folder construction.

asset/assetdomain/integration/integration.go (2)

86-92: Return cloned or safely handled slices.
The Webhooks() getter returns the original slice, allowing direct modification by the caller. Consider returning a copy to maintain immutability.

-func (i *Integration) Webhooks() []*Webhook {
-	return i.webhooks
+func (i *Integration) Webhooks() []*Webhook {
+	return slices.Clone(i.webhooks)
 }

163-175: Add test coverage for randomString edge cases.
The randomString function is a key mechanism for security tokens. Consider adding tests for zero or negative input, or for verifying the distribution of generated characters.

asset/assetinfrastructure/assetmemory/project.go (5)

46-48: Validate workspace read access more explicitly.
Although the code checks r.f.CanRead(v.Workspace()), consider returning an explicit error such as repo.ErrOperationDenied if the user doesn’t have read access, instead of silently filtering matching entries.


92-111: Clarify project ID vs alias usage.
When both pid and alias are empty (lines 99-101), you immediately return rerror.ErrNotFound. Consider returning an error that clarifies invalid input vs. resource not found, since no valid query was provided. This can help distinguish between a true “not found” scenario and invalid parameters.


137-144: Centralize error handling for CountByWorkspace.
The function returns 0 if the workspace is unreadable and 0 if the project is not found, which may merge two different cases (no projects vs. no read access). Returning a permission-denied error or differentiating these scenarios could improve observability.


165-169: Unify error messages for remove operation.
The Remove method returns rerror.ErrNotFound when the user lacks write access, making it ambiguous whether the resource truly doesn’t exist or the user just can’t modify it. Consider returning a clearer permission error instead.


172-178: Potential concurrency testing suggestion.
You provide MockProjectNow and SetProjectError for testing. You could also consider concurrency tests to ensure the underlying SyncMap usage remains safe under parallel read/writes.

asset/assetinfrastructure/assetfs/file.go (2)

45-53: Add logging or metrics for file read failures.
When fileUUID or filename is empty (line 47), you return an error. Logging could help diagnose usage issues.


127-133: Unsupported operations as part of the interface.
IssueUploadAssetLink and UploadedAsset return ErrUnsupportedOperation. Confirm if these are guaranteed by the domain or if stubs are expected to be eventually implemented.

asset/assetdomain/integration/integration_test.go (3)

84-127: Adequate coverage of constructor-like logic.
Testing that retrieved timestamps mirror the Integration ID timestamp is a neat approach, but consider also verifying edge cases, such as future-dated IDs (invalid) or explicit checks for zero/uninitialized timestamps.


703-773: Comprehensive webhook set capability.
The tests verify multiple scenarios (empty slice, non-empty slice). Looks good. Consider adding boundary checks on ID validity if required in production code (e.g., ensure no duplicates in the slice).


1310-1316: Token generation approach is straightforward.
Users can guess that "secret_" is the prefix. If security is a concern, consider a more random prefix or hashing approach. Alternatively, if secrecy is not a priority, this is fine.

asset/assetdomain/integration/integration_type.go (1)

13-22: Potential edge case in TypeFrom.
When the input is unrecognized, it defaults to TypePrivate. This is acceptable but be aware that invalid inputs might lead to an unintended private type. Consider adding logging or an error if unexpected values occur.

asset/assetdomain/asset/map.go (1)

13-20: Potential performance note for large maps.
When iterating over a large IDList, repeated map lookups are O(1) each, but can add up. This is likely fine for moderate data sizes.

asset/assetdomain/operator/id.go (1)

13-14: Consider documentation updates.
Providing doc comments for ErrInvalidID and NewIntegrationID can help future maintainers understand their usage.

asset/assetdomain/thread/common.go (1)

8-12: Consider enhancing error messages with more context

While the error messages are clear, they could be more specific to help with debugging and user experience.

Consider this enhancement:

-	ErrNoWorkspaceID       = rerror.NewE(i18n.T("workspace id is required"))
+	ErrNoWorkspaceID       = rerror.NewE(i18n.T("workspace id is required for thread operations"))
-	ErrCommentAlreadyExist = rerror.NewE(i18n.T("comment already exist in this thread"))
+	ErrCommentAlreadyExist = rerror.NewE(i18n.T("comment with this ID already exists in the thread"))
-	ErrCommentDoesNotExist = rerror.NewE(i18n.T("comment does not exist in this thread"))
+	ErrCommentDoesNotExist = rerror.NewE(i18n.T("comment with the specified ID does not exist in this thread"))
asset/assetdomain/asset/common.go (1)

8-14: Consider enhancing error messages for better debugging

While the error messages are clear, they could be more descriptive to aid in debugging. Consider adding more context about where and why these errors occur.

 var (
-	ErrNoProjectID = rerror.NewE(i18n.T("projectID is required"))
-	ErrZeroSize    = rerror.NewE(i18n.T("file size cannot be zero"))
-	ErrNoUser      = rerror.NewE(i18n.T("createdBy is required"))
-	ErrNoThread    = rerror.NewE(i18n.T("thread is required"))
-	ErrNoUUID      = rerror.NewE(i18n.T("uuid is required"))
+	ErrNoProjectID = rerror.NewE(i18n.T("asset creation failed: projectID is required"))
+	ErrZeroSize    = rerror.NewE(i18n.T("asset validation failed: file size cannot be zero"))
+	ErrNoUser      = rerror.NewE(i18n.T("asset creation failed: createdBy user is required"))
+	ErrNoThread    = rerror.NewE(i18n.T("asset operation failed: thread reference is required"))
+	ErrNoUUID      = rerror.NewE(i18n.T("asset identification failed: uuid is required"))
 )
asset/assetdomain/thread/list.go (1)

8-20: Add Map method for consistency

The Map method mentioned in the AI summary is missing from the implementation. This method would be useful for converting the list to a map keyed by thread IDs.

Would you like me to provide an implementation for the Map method to maintain consistency with other domain models?

asset/assetdomain/asset/list.go (1)

1-29: Well-structured implementation with good immutability practices.

The implementation demonstrates good practices:

  • Immutable operations returning new instances
  • Efficient use of modern Go libraries
  • Type-safe operations with proper error handling

Consider adding these methods for enhanced functionality:

  • Filter(predicate func(*Asset) bool) List for filtered views
  • Reduce(fn func(*Asset, *Asset) *Asset) *Asset for aggregations
asset/assetdomain/integration/id.go (1)

9-12: Consider generating ID types to reduce boilerplate.

The ID type definitions follow a consistent pattern across domains. Consider using code generation to maintain consistency and reduce duplication.

asset/assetdomain/asset/upload.go (1)

31-33: Improve time handling in Expired method

The current implementation could be enhanced to:

  1. Handle nil time.Time values
  2. Use time.Now() as default if no time is provided
  3. Add documentation explaining the behavior

Here's a suggested improvement:

+// Expired checks if the upload has expired.
+// If t is zero value, time.Now() is used.
 func (u *Upload) Expired(t time.Time) bool {
-    return t.After(u.expiresAt)
+    checkTime := t
+    if checkTime.IsZero() {
+        checkTime = time.Now()
+    }
+    return checkTime.After(u.expiresAt)
 }
asset/assetdomain/project/list_test.go (1)

9-27: Enhance test coverage for List_SortByID

The current test could be improved by:

  1. Adding test case for nil List
  2. Testing empty List
  3. Testing List with duplicate IDs

Here's a suggested addition:

 func TestList_SortByID(t *testing.T) {
+    tests := []struct {
+        name string
+        list List
+        want List
+    }{
+        {
+            name: "nil list",
+            list: nil,
+            want: nil,
+        },
+        {
+            name: "empty list",
+            list: List{},
+            want: List{},
+        },
         // existing test case...
+    }
+    for _, tt := range tests {
+        t.Run(tt.name, func(t *testing.T) {
+            got := tt.list.SortByID()
+            assert.Equal(t, tt.want, got)
+        })
+    }
 }
asset/assetdomain/integration/list.go (2)

10-10: Add documentation for public List type

The List type is exported but lacks documentation explaining its purpose and usage.

Add documentation:

+// List represents a collection of Integration pointers that can be sorted and filtered.
+// It provides methods for sorting by ID, cloning, and filtering active webhooks.
 type List []*Integration

3-8: Consider package organization and dependencies

The integration package depends on multiple external packages. Consider:

  1. Documenting the rationale for using lo vs standard library
  2. Moving common slice operations to a shared utility package
asset/assetdomain/thread/list_test.go (2)

10-28: Enhance test coverage for SortByID

While the basic sorting test is good, consider adding edge cases:

  • Empty list
  • Single item list
  • List with duplicate IDs
  • Nil list
func TestList_SortByID(t *testing.T) {
+       t.Run("normal case", func(t *testing.T) {
                id1 := NewID()
                id2 := NewID()
                list := List{
                        &Thread{id: id2},
                        &Thread{id: id1},
                }
                res := list.SortByID()
                assert.Equal(t, List{
                        &Thread{id: id1},
                        &Thread{id: id2},
                }, res)
                assert.Equal(t, List{
                        &Thread{id: id2},
                        &Thread{id: id1},
                }, list)
+       })
+       
+       t.Run("edge cases", func(t *testing.T) {
+               assert.Empty(t, List{}.SortByID())
+               assert.Empty(t, List(nil).SortByID())
+               
+               id := NewID()
+               singleList := List{&Thread{id: id}}
+               assert.Equal(t, singleList, singleList.SortByID())
+       })
}

30-37: Add edge cases to Clone test

Consider adding tests for edge cases in the Clone method:

  • Empty list
  • Nil list
  • List with nil elements
func TestList_Clone(t *testing.T) {
+       t.Run("normal case", func(t *testing.T) {
                th := New().NewID().Workspace(accountdomain.NewWorkspaceID()).MustBuild()
                list := List{th}
                got := list.Clone()
                assert.Equal(t, list, got)
                assert.NotSame(t, list[0], got[0])
+       })
+
+       t.Run("edge cases", func(t *testing.T) {
+               assert.Empty(t, List{}.Clone())
+               assert.Empty(t, List(nil).Clone())
+               
+               list := List{nil}
+               assert.Equal(t, list, list.Clone())
+       })
}
asset/assetdomain/asset/map_test.go (2)

14-14: Extract test asset creation into helper function

The asset creation chain is duplicated and quite long. Consider extracting it into a helper function for better maintainability and readability.

+func createTestAsset(pid ProjectID, uid accountdomain.UserID) *Asset {
+       return New().
+               NewID().
+               Project(pid).
+               CreatedByUser(uid).
+               Size(1000).
+               Thread(NewThreadID()).
+               NewUUID().
+               MustBuild()
+}
+
 func TestMap_List(t *testing.T) {
        pid := NewProjectID()
        uid := accountdomain.NewUserID()
-       a := New().NewID().Project(pid).CreatedByUser(uid).Size(1000).Thread(NewThreadID()).NewUUID().MustBuild()
+       a := createTestAsset(pid, uid)
        // ... rest of the test
 }

Also applies to: 24-24


10-18: Enhance test coverage with edge cases

Both test functions would benefit from additional test cases:

  • Map with multiple assets
  • Invalid/non-existent IDs in ListFrom
  • Empty ID list in ListFrom
  • Map with nil asset values
func TestMap_List(t *testing.T) {
+       t.Run("test cases", func(t *testing.T) {
                pid := NewProjectID()
                uid := accountdomain.NewUserID()
                a := createTestAsset(pid, uid)
+               b := createTestAsset(pid, uid)
                
-               assert.Equal(t, List{a}, Map{a.ID(): a}.List())
-               assert.Equal(t, List{}, Map(nil).List())
+               tests := []struct {
+                       name string
+                       m    Map
+                       want List
+               }{
+                       {"single asset", Map{a.ID(): a}, List{a}},
+                       {"multiple assets", Map{a.ID(): a, b.ID(): b}, List{a, b}},
+                       {"nil map", nil, List{}},
+                       {"map with nil value", Map{a.ID(): nil}, List{}},
+               }
+               
+               for _, tt := range tests {
+                       t.Run(tt.name, func(t *testing.T) {
+                               assert.Equal(t, tt.want, tt.m.List())
+                       })
+               }
+       })
}

Also applies to: 20-28

asset/assetdomain/asset/upload_builder.go (1)

7-9: Add documentation for the UploadBuilder type

Consider adding documentation comments to describe the purpose of the UploadBuilder and its usage pattern. This will improve code maintainability and help other developers understand how to use this builder correctly.

asset/assetdomain/operator/operator_test.go (1)

10-37: Consider adding more test cases for comprehensive coverage

The current test covers basic functionality but could benefit from additional test cases:

  • Invalid user/integration IDs
  • Empty/nil operators
  • Invalid state combinations

Would you like me to provide examples of additional test cases?

asset/assetdomain/operator/operator.go (1)

5-9: Consider adding JSON/GORM tags for persistence

If this Operator struct needs to be serialized or persisted, consider adding appropriate tags.

Example implementation:

type Operator struct {
-    user        *accountdomain.UserID
-    integration *IntegrationID
-    isMachine   bool
+    user        *accountdomain.UserID  `json:"user,omitempty" gorm:"column:user_id"`
+    integration *IntegrationID         `json:"integration,omitempty" gorm:"column:integration_id"`
+    isMachine   bool                   `json:"is_machine" gorm:"column:is_machine"`
}
asset/assetdomain/project/id.go (2)

35-40: Improve error handling in Alias method

The Alias method silently returns nil for empty strings. Consider making this behavior more explicit.

Consider this improvement:

func (i IDOrAlias) Alias() *string {
+    if string(i) == "" {
+        return nil
+    }
     if string(i) != "" && i.ID() == nil {
         return lo.ToPtr(string(i))
     }
     return nil
}

31-33: Consider caching ID conversion result

The ID() method performs conversion on every call, which could be inefficient if called frequently.

Consider implementing caching:

type IDOrAlias string

+type idOrAliasCache struct {
+    id    *ID
+    valid bool
+}
+
+var idCache sync.Map // map[IDOrAlias]idOrAliasCache

func (i IDOrAlias) ID() *ID {
+    if cache, ok := idCache.Load(i); ok {
+        return cache.(idOrAliasCache).id
+    }
+    id := IDFromRef(lo.ToPtr(string(i)))
+    idCache.Store(i, idOrAliasCache{id: id, valid: true})
     return id
}
asset/assetdomain/thread/builder.go (1)

16-26: Consider enhancing validation in Build method.

While the current validation checks for nil ID and workspace, consider adding validation for the comments slice to ensure it's not nil and that all comments are valid.

 func (b *Builder) Build() (*Thread, error) {
 	if b.th.id.IsNil() {
 		return nil, ErrInvalidID
 	}
 
 	if b.th.workspace.IsNil() {
 		return nil, ErrNoWorkspaceID
 	}
 
+	if b.th.comments == nil {
+		b.th.comments = []*Comment{}
+	}
+
+	for _, c := range b.th.comments {
+		if c == nil || c.ID().IsNil() {
+			return nil, ErrInvalidComment
+		}
+	}
+
 	return b.th, nil
 }
asset/assetdomain/thread/id.go (2)

9-12: Add documentation for exported type aliases.

These type aliases are part of the public API. Consider adding documentation comments to explain their purpose and usage.

+// ID represents a unique identifier for a Thread
 type ID = id.ThreadID
+// CommentID represents a unique identifier for a Comment within a Thread
 type CommentID = id.CommentID
+// UserID represents a unique identifier for a User
 type UserID = accountdomain.UserID
+// WorkspaceID represents a unique identifier for a Workspace
 type WorkspaceID = id.WorkspaceID

14-32: Consider grouping related declarations.

The ID-related functions could be organized better by grouping them based on their purpose (creation, validation, conversion).

+// Creation functions
 var NewID = id.NewThreadID
 var NewCommentID = id.NewCommentID
 var NewUserID = accountdomain.NewUserID
 var NewWorkspaceID = accountdomain.NewWorkspaceID
 
+// Validation functions
 var MustID = id.MustThreadID
 var MustCommentID = id.MustCommentID
 var MustUserID = id.MustUserID
 var MustWorkspaceID = id.MustWorkspaceID
 
+// Conversion functions
 var IDFrom = id.ThreadIDFrom
 var CommentIDFrom = id.CommentIDFrom
 var UserIDFrom = accountdomain.UserIDFrom
 var WorkspaceIDFrom = id.WorkspaceIDFrom
 
 var IDFromRef = id.ThreadIDFromRef
 var CommentIDFromRef = id.CommentIDFromRef
 var UserIDFromRef = accountdomain.UserIDFromRef
 var WorkspaceIDFromRef = id.WorkspaceIDFromRef
asset/assetinfrastructure/assetmemory/event.go (1)

13-16: Consider adding methods for event querying and filtering.

The current implementation only supports basic CRUD operations. Consider adding methods for:

  • Listing all events
  • Filtering events by type or timestamp
  • Pagination support
asset/assetdomain/thread/comment_test.go (2)

29-33: Use public methods in tests

The test is directly accessing the private content field. It's better to use public methods to maintain encapsulation.

 func TestComment_SetContent(t *testing.T) {
 	comment := Comment{}
 	comment.SetContent("xxx")
-	assert.Equal(t, "xxx", comment.content)
+	assert.Equal(t, "xxx", comment.Content())
 }

35-44: Consider using table-driven tests or sub-tests

The test covers multiple scenarios but could be more organized using Go's sub-tests.

 func TestComment_Clone(t *testing.T) {
-	comment := (&Comment{
-		id:      NewCommentID(),
-		author:  operator.OperatorFromUser(NewUserID()),
-		content: "test",
-	})
-	assert.Nil(t, (*Comment)(nil).Clone())
-	assert.Equal(t, comment, comment.Clone())
-	assert.NotSame(t, comment, comment.Clone())
+	t.Run("nil comment", func(t *testing.T) {
+		assert.Nil(t, (*Comment)(nil).Clone())
+	})
+
+	t.Run("valid comment", func(t *testing.T) {
+		comment := (&Comment{
+			id:      NewCommentID(),
+			author:  operator.OperatorFromUser(NewUserID()),
+			content: "test",
+		})
+		clone := comment.Clone()
+		assert.Equal(t, comment, clone)
+		assert.NotSame(t, comment, clone)
+	})
 }
asset/assetdomain/asset/list_test.go (2)

10-29: Add more test cases for sorting

While the test covers basic sorting, consider adding more cases:

  • Empty list
  • Single item list
  • List with duplicate IDs
  • Larger list to verify stable sort
 func TestList_SortByID(t *testing.T) {
-	id1 := NewID()
-	id2 := NewID()
-
-	list := List{
-		&Asset{id: id2},
-		&Asset{id: id1},
-	}
-	res := list.SortByID()
-
-	assert.Equal(t, List{
-		&Asset{id: id1},
-		&Asset{id: id2},
-	}, res)
-
-	assert.Equal(t, List{
-		&Asset{id: id2},
-		&Asset{id: id1},
-	}, list)
+	tests := []struct {
+		name string
+		list List
+		want List
+	}{
+		{
+			name: "empty list",
+			list: List{},
+			want: List{},
+		},
+		{
+			name: "single item",
+			list: List{&Asset{id: NewID()}},
+			want: List{&Asset{id: NewID()}},
+		},
+		{
+			name: "two items",
+			list: List{
+				&Asset{id: NewID()},
+				&Asset{id: NewID()},
+			},
+			want: List{
+				&Asset{id: NewID()},
+				&Asset{id: NewID()},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			original := tt.list.Clone()
+			got := tt.list.SortByID()
+			assert.Equal(t, tt.want, got)
+			assert.Equal(t, original, tt.list, "original list should not be modified")
+		})
+	}
 }

31-53: Reduce duplication and improve test structure

The tests have duplicate setup code and could benefit from table-driven tests for better organization.

Consider extracting common setup and using table-driven tests:

+func setupTestAsset(t *testing.T) *Asset {
+	t.Helper()
+	pid := NewProjectID()
+	uid := accountdomain.NewUserID()
+	return New().NewID().Project(pid).CreatedByUser(uid).
+		Size(1000).Thread(NewThreadID()).NewUUID().MustBuild()
+}
+
 func TestList_Clone(t *testing.T) {
-	pid := NewProjectID()
-	uid := accountdomain.NewUserID()
-	a := New().NewID().Project(pid).CreatedByUser(uid).Size(1000).Thread(NewThreadID()).NewUUID().MustBuild()
+	a := setupTestAsset(t)
 	list := List{a}
 	got := list.Clone()
 	assert.Equal(t, list, got)
 	assert.NotSame(t, list[0], got[0])
 }
 
 func TestList_Map(t *testing.T) {
-	pid := NewProjectID()
-	uid := accountdomain.NewUserID()
-	a := New().NewID().Project(pid).CreatedByUser(uid).Size(1000).Thread(NewThreadID()).NewUUID().MustBuild()
-
-	assert.Equal(t, Map{
-		a.ID(): a,
-	}, List{a, nil}.Map())
-	assert.Equal(t, Map{}, List(nil).Map())
+	tests := []struct {
+		name string
+		list List
+		want Map
+	}{
+		{
+			name: "nil list",
+			list: nil,
+			want: Map{},
+		},
+		{
+			name: "list with nil element",
+			list: List{nil},
+			want: Map{},
+		},
+		{
+			name: "valid list",
+			list: List{setupTestAsset(t)},
+			want: func() Map {
+				a := setupTestAsset(t)
+				return Map{a.ID(): a}
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.list.Map()
+			assert.Equal(t, tt.want, got)
+		})
+	}
 }
asset/assetdomain/event/builder.go (1)

18-23: Consider enhancing validation in Build method

The current validation only checks for nil ID and operator validation. Consider adding:

  • Timestamp validation (non-zero check)
  • Type validation (non-empty check)
  • Project validation (nil check)
 func (b *Builder[T]) Build() (*Event[T], error) {
-	if b.i.id.IsNil() || !b.i.operator.Validate() {
+	if b.i.id.IsNil() || !b.i.operator.Validate() || 
+		b.i.timestamp.IsZero() || b.i.ty == "" || b.i.prj == nil {
 		return nil, ErrInvalidID
 	}
 	return b.i, nil
 }
asset/assetdomain/asset/id.go (2)

9-14: Add documentation for ID types

Consider adding documentation comments for each type alias to explain their purpose and usage context.

+// ID represents a unique identifier for an asset
 type ID = assetdomain.AssetID
+// IDList represents a collection of asset IDs
 type IDList = assetdomain.AssetIDList
+// ProjectID represents a unique identifier for a project
 type ProjectID = assetdomain.ProjectID

16-36: Consider grouping related functions

The ID-related functions could be better organized by grouping them based on their functionality (e.g., constructors, validators, converters).

+// Constructors
 var NewID = assetdomain.NewAssetID
 var NewProjectID = assetdomain.NewProjectID
 var NewUserID = accountdomain.NewUserID
 var NewThreadID = assetdomain.NewThreadID
 var NewIntegrationID = assetdomain.NewIntegrationID

+// Must constructors - panic on error
 var MustID = assetdomain.MustAssetID
 var MustProjectID = assetdomain.MustProjectID
 var MustUserID = accountdomain.MustUserID
 var MustThreadID = assetdomain.MustThreadID

+// Conversion functions
 var IDFrom = assetdomain.AssetIDFrom
 var ProjectIDFrom = assetdomain.ProjectIDFrom
asset/assetdomain/project/publication.go (2)

3-10: Enhance type safety for PublicationScope

Consider using iota with string mapping for better type safety and performance.

+type PublicationScope int
+
 const (
-	PublicationScopePrivate PublicationScope = "private"
-	PublicationScopeLimited PublicationScope = "limited"
-	PublicationScopePublic  PublicationScope = "public"
+	PublicationScopePrivate PublicationScope = iota
+	PublicationScopeLimited
+	PublicationScopePublic
 )
+
+var publicationScopeNames = map[PublicationScope]string{
+	PublicationScopePrivate: "private",
+	PublicationScopeLimited: "limited",
+	PublicationScopePublic:  "public",
+}
+
+func (p PublicationScope) String() string {
+	return publicationScopeNames[p]
+}

46-55: Add documentation for Clone method

The Clone method implements defensive copying but lacks documentation explaining its behavior with nil receivers.

+// Clone creates a deep copy of the Publication.
+// Returns nil if the receiver is nil.
 func (p *Publication) Clone() *Publication {
asset/assetdomain/event/builder_test.go (2)

22-43: Consider adding more test cases for comprehensive coverage.

While the current test cases cover basic functionality, consider adding tests for:

  • Events with nil operator
  • Events with nil object
  • Events with invalid timestamps
  • Other event types defined in the system

26-29: Consider extracting test helper functions for event creation.

The event creation code is duplicated between ev and ev2. Consider extracting a helper function to reduce duplication and improve test maintainability.

+func createTestEvent[T any](id ID, timestamp time.Time, eventType Type, op operator.Operator, obj T) *Event[T] {
+    return New[T]().
+        ID(id).
+        Timestamp(timestamp).
+        Type(eventType).
+        Operator(op).
+        Object(obj).
+        MustBuild()
+}
asset/assetdomain/asset/file_builder.go (1)

68-74: Enhance content type detection.

The current implementation relies solely on file extension for content type detection. Consider:

  1. Using a more robust content type detection library
  2. Adding fallback mechanisms when extension is missing
asset/assetdomain/integration/webhook_builder.go (2)

18-26: Consider adding more validation checks in Build()

While the ID validation is good, consider adding additional validations for:

  • URL (non-nil and valid format)
  • Name (non-empty)
  • Trigger (valid value)
 func (b *WebhookBuilder) Build() (*Webhook, error) {
 	if b.w.id.IsNil() {
 		return nil, ErrInvalidID
 	}
+	if b.w.url == nil {
+		return nil, fmt.Errorf("url is required")
+	}
+	if b.w.name == "" {
+		return nil, fmt.Errorf("name is required")
+	}
 	if b.w.updatedAt.IsZero() {
 		b.w.updatedAt = b.w.CreatedAt()
 	}
 	return b.w, nil
 }

28-34: Consider returning error instead of panic in MustBuild

Using panic for control flow is generally discouraged in Go. Consider making this a regular Build call or documenting clearly that this should only be used in tests.

asset/assetdomain/event/event.go (1)

55-67: Improve Clone() implementation for deep copying

The current Clone() implementation performs a shallow copy of the object field. Consider:

  1. Adding a constraint on T to ensure it's cloneable
  2. Implementing deep copy for the object if needed
asset/assetdomain/asset/status.go (2)

17-33: Consider using a map for status conversion

The switch statement could be replaced with a map lookup for better maintainability and performance.

+var statusMap = map[string]ArchiveExtractionStatus{
+	"skipped":     ArchiveExtractionStatusSkipped,
+	"pending":     ArchiveExtractionStatusPending,
+	"in_progress": ArchiveExtractionStatusInProgress,
+	"done":        ArchiveExtractionStatusDone,
+	"failed":      ArchiveExtractionStatusFailed,
+}
+
 func ArchiveExtractionStatusFrom(s string) (ArchiveExtractionStatus, bool) {
 	ss := strings.ToLower(s)
-	switch ArchiveExtractionStatus(ss) {
-	case ArchiveExtractionStatusSkipped:
-		return ArchiveExtractionStatusSkipped, true
-	case ArchiveExtractionStatusPending:
-		return ArchiveExtractionStatusPending, true
-	case ArchiveExtractionStatusInProgress:
-		return ArchiveExtractionStatusInProgress, true
-	case ArchiveExtractionStatusDone:
-		return ArchiveExtractionStatusDone, true
-	case ArchiveExtractionStatusFailed:
-		return ArchiveExtractionStatusFailed, true
-	default:
-		return ArchiveExtractionStatus(""), false
-	}
+	status, ok := statusMap[ss]
+	if !ok {
+		return ArchiveExtractionStatus(""), false
+	}
+	return status, true
 }

51-57: Consider adding validation in StringRef()

The method assumes the status value is valid. Consider validating it before converting to string.

 func (s *ArchiveExtractionStatus) StringRef() *string {
 	if s == nil {
 		return nil
 	}
+	if _, ok := ArchiveExtractionStatusFrom(string(*s)); !ok {
+		return nil
+	}
 	s2 := string(*s)
 	return &s2
 }
asset/assetinfrastructure/assetmemory/asset_file.go (3)

16-20: Consider adding documentation for AssetFileImpl struct

The struct implements assetrepo.AssetFile interface but lacks documentation explaining its purpose and thread-safety guarantees provided by SyncMap.

Add documentation:

+// AssetFileImpl provides a thread-safe in-memory implementation of assetrepo.AssetFile interface.
+// It uses sync.Map internally to ensure concurrent access safety.
 type AssetFileImpl struct {

41-42: Commented code should be removed

There's a commented line that appears to be old implementation. Either remove it or document why it's kept.

-		// f = asset.FoldFiles(fs, f)
 		f.SetFiles(fs)

56-63: Consider adding validation for nil parameters

The SaveFlat method should validate that parent and files parameters are not nil before proceeding.

 func (r *AssetFileImpl) SaveFlat(ctx context.Context, id id.AssetID, parent *asset.File, files []*asset.File) error {
 	if r.err != nil {
 		return r.err
 	}
+	if parent == nil {
+		return rerror.ErrInvalid.WithMessage("parent file cannot be nil")
+	}
+	if files == nil {
+		files = []*asset.File{} // or return error based on requirements
+	}
 	r.data.Store(id, parent.Clone())
 	r.files.Store(id, slices.Clone(files))
 	return nil
 }
asset/assetdomain/integration/builder.go (1)

26-32: Reconsider using panic in MustBuild

While the pattern is common, consider if panic is appropriate for your use case. If kept, document the panic condition.

+// MustBuild is like Build but panics if the Integration cannot be built.
+// It should only be used in initialization code where Integration creation cannot fail.
 func (b *Builder) MustBuild() *Integration {
asset/assetdomain/asset/file_builder_test.go (2)

41-61: Enhance TestFileBuilder_Build test coverage

The test should include error cases and edge cases. Consider testing:

  • Empty paths
  • Invalid file extensions
  • Maximum file sizes
  • Empty/nil children arrays

Add test cases:

// Test error cases
f3 := NewFile().Name("").Build()
assert.Empty(t, f3.Name())

// Test edge cases
f4 := NewFile().Name("file").Path("/.").Size(0).Build()
assert.Equal(t, "/.", f4.Path())
assert.Zero(t, f4.Size())

// Test nil children
f5 := NewFile().Children(nil).Build()
assert.Nil(t, f5.Children())

21-29: Add test table for path handling

Consider using a test table to cover more path scenarios systematically.

func TestFileBuilder_Path(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"already_slashed", "/hoge", "/hoge"},
		{"needs_slash", "fuga", "/fuga"},
		{"empty", "", "/"},
		{"multiple_slashes", "//path", "/path"},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			f := NewFile().Path(tt.input).Build()
			assert.Equal(t, tt.expected, f.Path())
		})
	}
}
asset/assetdomain/project/builder.go (2)

72-81: Consider defensive copying in ImageURL method

The current implementation creates a copy of the URL to prevent modification of the original, which is good. However, the comment references a Go issue that's worth documenting more clearly.

Consider updating the comment to explain why copying is necessary:

-    // https://github.com/golang/go/issues/38351
+    // Create defensive copy of URL to prevent external modifications
+    // See: https://github.com/golang/go/issues/38351 for details on URL copying

93-96: Consider nil check for RequestRoles

While slices.Clone handles nil slices correctly, it's good practice to be explicit about nil handling for clarity and maintainability.

 func (b *Builder) RequestRoles(requestRoles []workspace.Role) *Builder {
+    if requestRoles == nil {
+        b.p.requestRoles = nil
+        return b
+    }
     b.p.requestRoles = slices.Clone(requestRoles)
     return b
 }
asset/assetdomain/asset/builder.go (1)

18-41: Consider adding size validation upper limit

The Build method validates for zero size but doesn't check for unreasonably large sizes.

 const (
+    MaxAssetSize = 1024 * 1024 * 1024 // 1GB or adjust as needed
 )

 func (b *Builder) Build() (*Asset, error) {
     if b.a.id.IsNil() {
         return nil, ErrInvalidID
     }
     if b.a.project.IsNil() {
         return nil, ErrNoProjectID
     }
     if b.a.user.IsNil() && b.a.integration.IsNil() {
         return nil, ErrNoUser
     }
     if b.a.thread.IsNil() {
         return nil, ErrNoThread
     }
     if b.a.size == 0 {
         return nil, ErrZeroSize
     }
+    if b.a.size > MaxAssetSize {
+        return nil, ErrAssetTooLarge
+    }
     if b.a.uuid == "" {
         return nil, ErrNoUUID
     }
asset/assetdomain/asset/asset.go (1)

10-23: Consider adding validation for required fields

The Asset struct contains fields that should be required (e.g., id, project, fileName), but there's no validation mechanism.

Consider adding a Validate method to ensure required fields are set:

func (a *Asset) Validate() error {
    if a == nil {
        return errors.New("nil asset")
    }
    if a.id.IsNil() {
        return errors.New("missing asset id")
    }
    if a.project.IsNil() {
        return errors.New("missing project id")
    }
    if a.fileName == "" {
        return errors.New("missing file name")
    }
    return nil
}
asset/assetinfrastructure/assetmemory/integration.go (2)

28-56: Consider extracting common error handling pattern

Both methods share the same error handling pattern. Consider extracting it into a helper method to reduce duplication.

+func (r *Integration) checkError() error {
+    if r.err != nil {
+        return r.err
+    }
+    return nil
+}

 func (r *Integration) FindByID(_ context.Context, iId id.IntegrationID) (*integration.Integration, error) {
-    if r.err != nil {
-        return nil, r.err
-    }
+    if err := r.checkError(); err != nil {
+        return nil, err
+    }

82-101: Consider adding validation in Save method

While the implementation is correct, consider adding validation before storing the integration:

  • Check if integration is nil
  • Validate required fields
 func (r *Integration) Save(_ context.Context, i *integration.Integration) error {
     if r.err != nil {
         return r.err
     }
+    if i == nil {
+        return rerror.ErrInvalidParameter
+    }
     r.data.Store(i.ID(), i)
     return nil
 }
asset/assetdomain/project/project_test.go (3)

13-37: Fix typo and enhance test coverage

  1. Fix the typo in "expexted" to "expected"
  2. Consider adding more test cases for edge cases:
    • Empty string
    • Special characters
    • Maximum length
 testCase := []struct {
     name, alias string
-    expexted    bool
+    expected    bool
 }{

39-84: Well-structured tests with good practices

Good use of:

  • Table-driven tests
  • Parallel test execution
  • Clear assertions

Consider adding error cases for SetImageURL with invalid URLs.


86-113: Consider adding more test cases

The tests cover basic functionality but could benefit from additional cases:

  • Nil checks for Publication
  • Empty/invalid descriptions
  • Invalid team IDs
  • Empty/nil request roles
asset/assetdomain/asset/asset_test.go (2)

12-56: Improve test readability and maintainability

Consider:

  1. Extracting test constants for magic values
  2. Using helper functions for common setup
  3. Breaking down the large test into smaller, focused tests
+const (
+    testUUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+    testTime = "2021-03-16T04:19:57.592Z"
+    testFileName = "hoge"
+)
+
+func setupTestAsset() *Asset {
+    return &Asset{
+        id: NewID(),
+        project: NewProjectID(),
+        // ... other common setup
+    }
+}

58-96: Reduce test setup duplication

Consider converting these tests to table-driven tests to reduce setup duplication and make it easier to add new test cases.

+func TestAsset_Getters(t *testing.T) {
+    tests := []struct {
+        name string
+        setup func(*Asset)
+        check func(*testing.T, *Asset)
+    }{
+        {
+            name: "PreviewType nil",
+            setup: func(a *Asset) {},
+            check: func(t *testing.T, a *Asset) {
+                assert.Nil(t, a.PreviewType())
+            },
+        },
+        // Add more cases
+    }
+    // ... test execution
+}
asset/assetdomain/integration/list_test.go (4)

14-32: Consider adding more test cases for SortByID.

The test covers basic sorting functionality and immutability, but could be enhanced with additional cases:

  • Empty list
  • Single item list
  • List with duplicate IDs

34-44: Enhance clone test coverage.

Consider adding test cases for:

  • Empty list cloning
  • List with multiple items
  • Nested object modifications

153-157: Remove commented-out code.

The commented-out test struct definition should be removed as it's no longer needed.


57-151: Consider refactoring test data setup.

The test data setup is quite verbose. Consider creating helper functions to build test data, such as:

func createTestWebhook(id WebhookID, name string, active bool, triggers WebhookTrigger) *Webhook
func createTestIntegration(id ID, name string, webhooks []*Webhook) *Integration
asset/assetinfrastructure/assetfs/file_test.go (1)

33-55: Add content type and size assertions.

Consider adding assertions for:

  • File content type verification
  • File size checks
  • Buffer handling for large files
asset/assetdomain/asset/preview_type_test.go (2)

11-130: Consider using test case struct as a named type.

The test case struct is reused across multiple tests. Consider defining it as a named type to improve code reusability:

type previewTypeTestCase struct {
    Name     string
    Expected struct {
        TA   PreviewType
        Bool bool
    }
}

211-228: Add more edge cases to DetectPreviewType test.

Consider adding test cases for:

  • Files without extensions
  • Mixed case extensions
  • Multiple extensions (e.g., file.tar.gz)
asset/assetdomain/asset/file_test.go (4)

9-30: Rename test function to better reflect its purpose

The function name TestFile_FileType doesn't accurately represent what it's testing. The test actually verifies multiple file properties and methods, not just the file type.

Consider renaming to TestFile_BasicProperties or TestFile_CoreFunctionality to better reflect its purpose.

Additionally, consider adding edge cases for content type guessing:

  • Empty files
  • Files with unusual extensions
  • Files without extensions

32-43: Add more test cases for children handling

While the current test covers basic scenarios, consider adding tests for:

  • Maximum depth of nested children
  • Circular references
  • Empty children array vs nil children

45-71: Convert to table-driven tests for better maintainability

Consider restructuring this test as a table-driven test to make it easier to add more test cases and improve readability. This would also make it easier to test edge cases like:

  • Empty directories
  • Files with same names in different directories
  • Very deep directory structures

73-84: Add validation for file relationships

Consider adding assertions to verify:

  • Parent-child relationships are maintained
  • File paths are consistent with the hierarchy
  • Duplicate files are handled correctly
asset/assetdomain/project/builder_test.go (2)

14-34: Add more test cases for builder initialization

While the basic initialization is tested, consider adding test cases for:

  • Builder with pre-initialized values
  • Builder with invalid initial state

113-202: Consider testing concurrent builder usage

The builder pattern implementation should be tested for concurrent usage safety. Consider adding test cases that verify the builder's behavior when used concurrently.

Example test:

func TestBuilder_ConcurrentUsage(t *testing.T) {
    builder := New()
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // Perform concurrent builder operations
        }()
    }
    wg.Wait()
}
asset/assetdomain/asset/builder_test.go (2)

12-31: Consider adding validation for input fields.

The Input struct is used to test the builder but lacks field validation. Consider adding validation tags or documentation to clarify field requirements.

 type Input struct {
-	id                      ID
-	project                 ProjectID
-	createdAt               time.Time
-	createdByUser           accountdomain.UserID
-	createdByIntegration    IntegrationID
-	fileName                string
-	size                    uint64
-	previewType             *PreviewType
-	uuid                    string
-	thread                  ThreadID
-	archiveExtractionStatus *ArchiveExtractionStatus
+	id                      ID                        `validate:"required"`
+	project                 ProjectID                 `validate:"required"`
+	createdAt               time.Time                 
+	createdByUser           accountdomain.UserID      
+	createdByIntegration    IntegrationID            
+	fileName                string                    `validate:"required"`
+	size                    uint64                    `validate:"required,gt=0"`
+	previewType             *PreviewType             
+	uuid                    string                    `validate:"required,uuid"`
+	thread                  ThreadID                  `validate:"required"`
+	archiveExtractionStatus *ArchiveExtractionStatus 
 }

316-322: Consider testing NewID with multiple goroutines.

The TestBuilder_NewID function should verify that IDs are unique when generated concurrently.

 func TestBuilder_NewID(t *testing.T) {
+	const numGoroutines = 100
+	ids := make(chan ID, numGoroutines)
+	var wg sync.WaitGroup
+	wg.Add(numGoroutines)
+
+	for i := 0; i < numGoroutines; i++ {
+		go func() {
+			defer wg.Done()
+			a := New().NewID().Project(pid).CreatedByUser(uid).Size(size).Thread(NewThreadID()).NewUUID().MustBuild()
+			ids <- a.id
+		}()
+	}
+
+	wg.Wait()
+	close(ids)
+
+	// Verify uniqueness
+	seen := make(map[ID]bool)
+	for id := range ids {
+		assert.False(t, seen[id], "Duplicate ID generated")
+		seen[id] = true
+	}
 }
asset/assetdomain/integration/webhook_test.go (1)

493-501: Add comprehensive trigger combination tests.

The webhook trigger tests should verify all possible combinations of triggers and their interactions.

 func TestWebhookTrigger_Enable(t *testing.T) {
-	wt := WebhookTrigger{}
-
-	assert.False(t, wt.IsActive(event.ItemCreate))
-	wt.Enable(event.ItemCreate)
-	assert.True(t, wt.IsActive(event.ItemCreate))
-	wt.Disable(event.ItemCreate)
-	assert.False(t, wt.IsActive(event.ItemCreate))
+	tests := []struct {
+		name string
+		setup func(*WebhookTrigger)
+		check func(*testing.T, *WebhookTrigger)
+	}{
+		{
+			name: "single_trigger",
+			setup: func(wt *WebhookTrigger) {
+				wt.Enable(event.ItemCreate)
+			},
+			check: func(t *testing.T, wt *WebhookTrigger) {
+				assert.True(t, wt.IsActive(event.ItemCreate))
+				assert.False(t, wt.IsActive(event.ItemUpdate))
+			},
+		},
+		{
+			name: "multiple_triggers",
+			setup: func(wt *WebhookTrigger) {
+				wt.Enable(event.ItemCreate)
+				wt.Enable(event.ItemUpdate)
+				wt.Enable(event.ItemDelete)
+			},
+			check: func(t *testing.T, wt *WebhookTrigger) {
+				assert.True(t, wt.IsActive(event.ItemCreate))
+				assert.True(t, wt.IsActive(event.ItemUpdate))
+				assert.True(t, wt.IsActive(event.ItemDelete))
+			},
+		},
+		{
+			name: "toggle_triggers",
+			setup: func(wt *WebhookTrigger) {
+				wt.Enable(event.ItemCreate)
+				wt.Disable(event.ItemCreate)
+				wt.Enable(event.ItemCreate)
+			},
+			check: func(t *testing.T, wt *WebhookTrigger) {
+				assert.True(t, wt.IsActive(event.ItemCreate))
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			wt := WebhookTrigger{}
+			tt.setup(&wt)
+			tt.check(t, &wt)
+		})
+	}
 }
asset/assetinfrastructure/assetmemory/asset_test.go (4)

17-73: Consider adding more test cases for comprehensive permission testing.

While the current test cases cover basic scenarios, consider adding these cases for better coverage:

  1. Partial permissions (readable but not writable)
  2. Empty project list vs nil project list
  3. Mixed permissions across multiple projects

Example additional test case:

 tests := []struct {
     name    string
     seeds   asset.List
     arg     repo.ProjectFilter
     wantErr error
     mockErr bool
 }{
+    {
+        name: "project filter with read-only permission",
+        seeds: asset.List{
+            p1,
+            p2,
+        },
+        arg: repo.ProjectFilter{
+            Readable: []id.ProjectID{tid1},
+            Writable: []id.ProjectID{},
+        },
+        wantErr: nil,
+    },

75-156: Consider adding test case for invalid ID format.

The test suite is comprehensive but could be enhanced by adding a test case for invalid ID format, although this might be handled at the domain level.

Example additional test case:

 tests := []struct {
     name    string
     seeds   []*asset.Asset
     arg     id.AssetID
     want    *asset.Asset
     wantErr error
 }{
+    {
+        name:    "Invalid ID format",
+        seeds:   []*asset.Asset{a1},
+        arg:     id.AssetID{}, // or invalid format if supported
+        want:    nil,
+        wantErr: rerror.ErrInvalidID,
+    },

158-252: Verify order of returned assets matches input order.

The test cases should verify that the order of returned assets matches the order of requested IDs, as this is often important for maintaining consistency in the UI.

Example modification:

 {
     name: "2 count with multi assets",
     seeds: []*asset.Asset{
         a1,
         a2,
     },
     arg:     id.AssetIDList{id2, id1}, // Reversed order
-    want:    []*asset.Asset{a1, a2},
+    want:    []*asset.Asset{a2, a1}, // Should match input order
     wantErr: nil,
 },

1-456: Well-structured test suite with room for enhancement.

The test suite follows Go best practices and provides good coverage. Consider these improvements:

  1. Add package documentation explaining the purpose of these tests
  2. Consider using test helper functions for common setup code
  3. Add more edge cases across all test functions

Add package documentation:

+// Package assetmemory provides an in-memory implementation of the asset repository.
+// This test file verifies the implementation meets the repository interface contract.
 package assetmemory
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94879db and 7f1d1ee.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (82)
  • .gitignore (1 hunks)
  • asset/asset.graphql (1 hunks)
  • asset/assetdomain/asset/asset.go (1 hunks)
  • asset/assetdomain/asset/asset_test.go (1 hunks)
  • asset/assetdomain/asset/builder.go (1 hunks)
  • asset/assetdomain/asset/builder_test.go (1 hunks)
  • asset/assetdomain/asset/common.go (1 hunks)
  • asset/assetdomain/asset/file.go (1 hunks)
  • asset/assetdomain/asset/file_builder.go (1 hunks)
  • asset/assetdomain/asset/file_builder_test.go (1 hunks)
  • asset/assetdomain/asset/file_test.go (1 hunks)
  • asset/assetdomain/asset/id.go (1 hunks)
  • asset/assetdomain/asset/list.go (1 hunks)
  • asset/assetdomain/asset/list_test.go (1 hunks)
  • asset/assetdomain/asset/map.go (1 hunks)
  • asset/assetdomain/asset/map_test.go (1 hunks)
  • asset/assetdomain/asset/preview_type.go (1 hunks)
  • asset/assetdomain/asset/preview_type_test.go (1 hunks)
  • asset/assetdomain/asset/status.go (1 hunks)
  • asset/assetdomain/asset/status_test.go (1 hunks)
  • asset/assetdomain/asset/upload.go (1 hunks)
  • asset/assetdomain/asset/upload_builder.go (1 hunks)
  • asset/assetdomain/event/builder.go (1 hunks)
  • asset/assetdomain/event/builder_test.go (1 hunks)
  • asset/assetdomain/event/event.go (1 hunks)
  • asset/assetdomain/event/event_test.go (1 hunks)
  • asset/assetdomain/event/id.go (1 hunks)
  • asset/assetdomain/file/file.go (1 hunks)
  • asset/assetdomain/file/file_test.go (1 hunks)
  • asset/assetdomain/file/testdata/test.txt (1 hunks)
  • asset/assetdomain/id.go (1 hunks)
  • asset/assetdomain/integration/builder.go (1 hunks)
  • asset/assetdomain/integration/builder_test.go (1 hunks)
  • asset/assetdomain/integration/id.go (1 hunks)
  • asset/assetdomain/integration/integration.go (1 hunks)
  • asset/assetdomain/integration/integration_test.go (1 hunks)
  • asset/assetdomain/integration/integration_type.go (1 hunks)
  • asset/assetdomain/integration/integration_type_test.go (1 hunks)
  • asset/assetdomain/integration/list.go (1 hunks)
  • asset/assetdomain/integration/list_test.go (1 hunks)
  • asset/assetdomain/integration/webhook.go (1 hunks)
  • asset/assetdomain/integration/webhook_builder.go (1 hunks)
  • asset/assetdomain/integration/webhook_builder_test.go (1 hunks)
  • asset/assetdomain/integration/webhook_test.go (1 hunks)
  • asset/assetdomain/operator/id.go (1 hunks)
  • asset/assetdomain/operator/operator.go (1 hunks)
  • asset/assetdomain/operator/operator_test.go (1 hunks)
  • asset/assetdomain/project/builder.go (1 hunks)
  • asset/assetdomain/project/builder_test.go (1 hunks)
  • asset/assetdomain/project/id.go (1 hunks)
  • asset/assetdomain/project/id_test.go (1 hunks)
  • asset/assetdomain/project/list.go (1 hunks)
  • asset/assetdomain/project/list_test.go (1 hunks)
  • asset/assetdomain/project/project.go (1 hunks)
  • asset/assetdomain/project/project_test.go (1 hunks)
  • asset/assetdomain/project/publication.go (1 hunks)
  • asset/assetdomain/project/publication_test.go (1 hunks)
  • asset/assetdomain/task/task.go (1 hunks)
  • asset/assetdomain/thread/builder.go (1 hunks)
  • asset/assetdomain/thread/builder_test.go (1 hunks)
  • asset/assetdomain/thread/comment.go (1 hunks)
  • asset/assetdomain/thread/comment_test.go (1 hunks)
  • asset/assetdomain/thread/common.go (1 hunks)
  • asset/assetdomain/thread/id.go (1 hunks)
  • asset/assetdomain/thread/list.go (1 hunks)
  • asset/assetdomain/thread/list_test.go (1 hunks)
  • asset/assetdomain/thread/thread.go (1 hunks)
  • asset/assetdomain/thread/thread_test.go (1 hunks)
  • asset/assetinfrastructure/assetfs/common.go (1 hunks)
  • asset/assetinfrastructure/assetfs/file.go (1 hunks)
  • asset/assetinfrastructure/assetfs/file_test.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/asset.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/asset_file.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/asset_test.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/container.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/container_test.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/event.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/event_test.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/integration.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/integration_test.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/project.go (1 hunks)
  • asset/assetinfrastructure/assetmemory/project_test.go (1 hunks)
⛔ Files not processed due to max files limit (23)
  • asset/assetinfrastructure/assetmemory/thread.go
  • asset/assetinfrastructure/assetmemory/thread_test.go
  • asset/assetusecase/assetgateway/container.go
  • asset/assetusecase/assetgateway/file.go
  • asset/assetusecase/assetgateway/gatewaymock/task.go
  • asset/assetusecase/assetgateway/genmock.go
  • asset/assetusecase/assetgateway/task.go
  • asset/assetusecase/assetinteractor/asset.go
  • asset/assetusecase/assetinteractor/asset_test.go
  • asset/assetusecase/assetinteractor/common.go
  • asset/assetusecase/assetinteractor/common_test.go
  • asset/assetusecase/assetinterfaces/asset.go
  • asset/assetusecase/assetinterfaces/common.go
  • asset/assetusecase/assetrepo/asset.go
  • asset/assetusecase/assetrepo/asset_upload.go
  • asset/assetusecase/assetrepo/container.go
  • asset/assetusecase/assetrepo/event.go
  • asset/assetusecase/assetrepo/intgration.go
  • asset/assetusecase/assetrepo/project.go
  • asset/assetusecase/assetrepo/thread.go
  • asset/assetusecase/operator.go
  • asset/assetusecase/operator_test.go
  • go.mod
✅ Files skipped from review due to trivial changes (3)
  • asset/assetdomain/file/testdata/test.txt
  • .gitignore
  • asset/assetinfrastructure/assetfs/common.go
🔇 Additional comments (50)
asset/assetdomain/integration/integration_type_test.go (1)

9-39: Well-structured test implementation!

The test implementation follows several good practices:

  • Table-driven tests with clear structure
  • Parallel test execution with proper loop variable handling
  • Clear test case naming and organization
  • Secure default behavior for unknown values
asset/assetinfrastructure/assetmemory/integration_test.go (1)

16-94: Solid test structure and mocking approach.

The test function “TestIntegrationRepo_FindByID” employs a clear table-driven testing pattern, thorough seeding logic, and mock error injection. Parallel sub-tests ensure faster feedback. This structure is consistent and covers both positive and negative cases.

asset/assetdomain/project/publication_test.go (4)

1-8: LGTM! Clean and minimal imports.

The package declaration and imports are appropriate for a test file, using the standard testing package and testify/assert for assertions.


37-65: LGTM! Comprehensive scope transition testing.

The test covers all possible scope transitions and handles the empty scope case correctly.

Consider using table-driven tests as suggested earlier for better maintainability.


67-83: LGTM! Complete coverage of asset visibility transitions.

The test thoroughly verifies both transitions of the asset visibility state.


85-94: LGTM! Thorough clone testing.

The test comprehensively verifies the cloning functionality:

  • Ensures equality between original and clone
  • Confirms deep copy with different memory addresses
  • Properly handles the nil case
asset/assetdomain/thread/thread_test.go (2)

28-37: Add safety check in Thread.Comments() method

The test reveals that the Comments() method is expected to handle nil receivers. Ensure that the actual Thread.Comments() method includes a nil check to prevent panics in production.


74-109: 🛠️ Refactor suggestion

Add tests for edge cases in update and delete operations

Consider adding tests for:

  1. Updating a comment with empty content
  2. Concurrent modifications (if thread operations should be thread-safe)
  3. Maximum content length validation (if applicable)
asset/assetinfrastructure/assetmemory/event_test.go (1)

3-16: LGTM! Imports are well-organized.

The imports are properly structured and all dependencies are being used in the test file.

asset/assetdomain/file/file.go (1)

24-54: Validate presence of multipart form data.
The loop terminates after encountering the first matching part, but there's no stricter check that multipart data actually contains valid content (e.g., correct or non-empty). In some edge cases, an empty upload might pass. Consider additional validation on the file size or a minimal read to ensure the part is valid prior to returning.

asset/assetdomain/asset/preview_type.go (1)

20-24: Assess usage of custom type for references.
Using a pointer to a custom string type (PreviewType) is valid, but be aware that other code might expect a plain string pointer. Ensure consistent usage within the codebase to avoid confusion or accidental nil-pointer dereferences.

asset/asset.graphql (1)

51-57: Clarify concurrency rules for CreateAssetInput fields.
Since file uploads can be large or asynchronous, watch for potential concurrency issues if combining file-based input with URL-based input. Ensure that the server logic handles concurrency gracefully.

asset/assetdomain/asset/file.go (1)

63-67: Verify logic that excludes the parent path from SetFiles.
When filtering child files, confirm that ignoring any file with exactly the same path as the parent does not cause edge cases (e.g., file name collisions).

asset/assetinfrastructure/assetmemory/project.go (1)

118-120: Check for potential edge cases in blank name retrieval.
“FindByPublicName” returns nil if name is empty. Ensure there are no scenarios where an empty alias is a valid input.

asset/assetinfrastructure/assetfs/file.go (4)

27-37: Handle invalid or missing defaultBase more gracefully.
Line 30 sets urlBase to defaultBase if empty, but defaultBase is not shown in this snippet. Confirm that it is defined and is valid to avoid runtime issues.


92-98: Check file size limits more thoroughly.
You reject files ≥ fileSizeLimit, but the snippet does not show its definition or usage. Ensure that the limit is set to a reasonable default and is consistently applied.


122-125: Escaping asset filenames.
Ensure that path escaping in GetURL (line 124) is sufficient to avoid any potential path injection vulnerabilities.


193-199: Confirm path composition for potential path traversal vulnerabilities.
getFSObjectPath simply concatenates the assetDir, the first two characters, and the rest of fileUUID. Confirm that input can’t be manipulated to navigate outside the desired directory.

asset/assetdomain/id.go (1)

1-239: No critical issues detected.
All newly declared ID types, builder functions, and aliases appear consistent. The file is primarily domain definitions, with minimal logic. No major concerns.

asset/assetinfrastructure/assetmemory/project_test.go (8)

19-130: Good coverage for CountByWorkspace.
The tests methodically check different seeds, filters, and error mocks. This ensures robust coverage for workspace-based queries.


132-195: Filtered tests handle workspace permissions effectively.
The approach of passing a repo.WorkspaceFilter to validate r/w constraints is well structured.


197-310: Comprehensive coverage of FindByID.
Edge cases for filtering, missing projects, and error mocking are thoroughly tested. All looks good.


312-444: Extensive tests for FindByIDs.
Including scenarios with multiple seeds, filters, and error injection sets a strong foundation.


446-584: Consistent approach for FindByPublicName.
The results reflect correct usage of mocks, filters, and alias lookups.


586-732: Methodical pagination checks in FindByWorkspaces.
Though pagination is partially indicated as a TODO in code, this test suite verifies fundamental behavior with multi-page scenarios.


734-840: Remove operation tested thoroughly.
Different seeds, workspace filters, and error states highlight valuable coverage.


842-940: Save operation test.
These tests confirm read/write constraints, mocking errors, and verifying that the newly saved project is retrievable.

asset/assetdomain/integration/integration_test.go (2)

19-19: Great use of dynamic IDs.
Generating test-specific IDs (e.g., iId, uId) helps ensure isolation in concurrent tests and reduces the chance of ID collisions.


75-80: Parallelized subtests are beneficial, but verify resource safety.
Parallel execution can lead to race conditions if the tests share resources like global variables or environment settings. Make sure that all data is ephemeral or is set up/teardown in a thread-safe manner.

asset/assetdomain/integration/integration_type.go (1)

7-11: Enums are effectively represented.
Using string-based type definitions for "public"/"private" is a convenient approach for easy JSON serialization and comparisons. Just ensure all references use these constants rather than hard-coded strings.

asset/assetdomain/asset/map.go (1)

7-11: Efficient usage of lo.MapToSlice.
This approach keeps the code concise and avoids manual iteration. Good job leveraging the library’s utility.

asset/assetdomain/operator/id.go (1)

9-11: Clear type aliasing for domain concepts.
Aliasing types clarifies domain ownership, ensuring that the code cleanly integrates with the existing ID definitions. This is straightforward and readable.

asset/assetdomain/thread/list.go (1)

18-20: Verify thread cloning implementation

The Clone method assumes that Thread.Clone() exists and properly implements deep copying. Let's verify this assumption.

✅ Verification successful

Thread cloning implementation is properly implemented

The verification confirms that:

  • Thread.Clone() exists and implements proper deep copying
  • It handles nil receivers correctly
  • It clones all fields: id, workspace, and comments
  • Comment.Clone() is also implemented correctly
  • The implementation is tested with proper assertions for nil handling and deep copying
  • util.Map is used correctly for cloning comment slices
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if Thread.Clone() exists and its implementation
ast-grep --pattern 'func (t *Thread) Clone() *Thread {
  $$$
}'

Length of output: 65


Script:

#!/bin/bash
# Let's first find the Thread type definition and its methods
rg "type Thread struct" -A 5

# Also search for Clone method with a more flexible pattern
rg "func.*Thread.*Clone"

# Let's also check the util.Map implementation since it's used in the Clone method
rg "func Map"

Length of output: 1888


Script:

#!/bin/bash
# Let's check the Thread.Clone() implementation and its test
rg "func \(th \*Thread\) Clone\(\)" -A 10 asset/assetdomain/thread/thread.go

# Also check the test implementation for additional context
rg "func TestThread_Clone" -A 10 asset/assetdomain/thread/thread_test.go

# Check util.Map implementation since it's used in List.Clone()
rg "func Map\[T any, V any\]" -A 5 util/slice.go

Length of output: 866


Script:

#!/bin/bash
# Let's check the Comment.Clone() implementation since it's used in Thread.Clone()
rg "func \(c \*Comment\) Clone\(\)" -A 10 asset/assetdomain/thread/

# Also check if there's any more of the Thread.Clone() implementation we didn't see
rg "func \(th \*Thread\) Clone\(\)" -A 15 asset/assetdomain/thread/thread.go

Length of output: 960

asset/assetdomain/asset/list.go (1)

23-29: LGTM! Robust nil handling in Map conversion.

The Map method correctly filters out nil entries before conversion and uses efficient functional operations from the lo library. The nil check prevents potential panics during map creation.

asset/assetdomain/event/id.go (2)

1-17: Well-organized domain separation with clear type hierarchies.

The code demonstrates good domain-driven design practices:

  • Clear separation of concerns between account and asset domains
  • Consistent type aliasing for domain objects
  • Complete set of ID operations

3-7: Verify the version compatibility of imported packages.

The code imports packages from multiple domains. Let's ensure version compatibility.

asset/assetdomain/integration/id.go (1)

14-19: Consistent error handling across domains.

Good use of centralized error definition with ErrInvalidID. The ID operations provide a complete set of functionality.

✅ Verification successful

The search results show consistent error handling across domains, where ErrInvalidID is defined in the central idx package and reused across different domain packages. Each domain properly imports and uses this error, maintaining consistency in error handling for invalid IDs. The original review comment's approval of the centralized error definition is accurate.

Consistent error handling pattern confirmed across domains

The codebase demonstrates a uniform approach to ID-related error handling:

  • Central definition in idx/id.go
  • Consistent reuse across domain packages
  • Proper error propagation in builder patterns
  • Consistent testing patterns for ID validation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent error handling across domains
rg "ErrInvalidID" --type go

Length of output: 2866

asset/assetinfrastructure/assetmemory/container.go (1)

11-23: Consider adding error handling for repository initialization

The New function assumes all repository initializations will succeed. Consider adding error handling to catch potential initialization failures, especially since this is part of a microservice architecture.

Additionally, using NopTransaction in production could lead to data consistency issues.

Let's verify if this container is used in tests only:

asset/assetdomain/thread/builder.go (2)

8-14: LGTM! Well-structured builder pattern implementation.

The builder pattern is implemented correctly with proper encapsulation and a clean constructor function.


51-54: 🛠️ Refactor suggestion

Ensure thread-safety in Comments method.

While slices.Clone provides a shallow copy of the slice, the Comment objects themselves are still shared. Consider deep copying the comments to prevent potential race conditions if the original comments are modified after building.

 func (b *Builder) Comments(c []*Comment) *Builder {
-	b.th.comments = slices.Clone(c)
+	comments := make([]*Comment, len(c))
+	for i, comment := range c {
+		if comment != nil {
+			comments[i] = comment.Clone()
+		}
+	}
+	b.th.comments = comments
 	return b
 }
asset/assetdomain/event/event_test.go (1)

15-31: 🛠️ Refactor suggestion

Split test into focused sub-tests and add error cases

The test covers multiple aspects but could be improved in several ways:

  1. Split into focused sub-tests for better organization
  2. Add error cases
  3. Use fixed time instead of time.Now()

Consider restructuring like this:

-func TestEvent(t *testing.T) {
+func TestEvent(t *testing.T) {
+	fixedTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
+	
+	setupTestEvent := func() (*user.User, *asset.Asset, Event[*asset.Asset]) {
+		u := user.New().NewID().Email("[email protected]").Name("John").MustBuild()
+		a := asset.New().NewID().Thread(id.NewThreadID()).NewUUID().
+			Project(project.NewID()).Size(100).CreatedByUser(u.ID()).MustBuild()
+		ev := New[*asset.Asset]().ID(NewID()).Timestamp(fixedTime).Type(AssetCreate).
+			Operator(operator.OperatorFromUser(u.ID())).Object(a).MustBuild()
+		return u, a, ev
+	}
+
+	t.Run("successful creation", func(t *testing.T) {
+		u, a, ev := setupTestEvent()
+		assert.Equal(t, Type(AssetCreate), ev.Type())
+		assert.Equal(t, operator.OperatorFromUser(u.ID()), ev.Operator())
+		assert.Equal(t, a, ev.Object())
+		assert.Equal(t, fixedTime, ev.Timestamp())
+	})
+
+	t.Run("cloning", func(t *testing.T) {
+		_, _, ev := setupTestEvent()
+		clone := ev.Clone()
+		assert.Equal(t, ev, clone)
+		assert.NotSame(t, ev, clone)
+	})
+
+	t.Run("error cases", func(t *testing.T) {
+		t.Run("missing required fields", func(t *testing.T) {
+			_, err := New[*asset.Asset]().Build()
+			assert.Error(t, err)
+		})
+	})
+}

Let's verify if there are any error cases we should test:

asset/assetinfrastructure/assetmemory/integration.go (3)

15-26: Well-structured implementation with good practices!

The implementation shows good attention to thread-safety using SyncMap and testability with the TimeNow utility.


58-80: Well-implemented bulk operations with consistent output

Good practices observed:

  • Efficient bulk operations using FindAll
  • Consistent sorting of results
  • Clear error handling

103-109: Well-designed testing utilities

The mock functions provide good testing capabilities while maintaining type safety.

asset/assetdomain/project/project_test.go (1)

115-160: Well-implemented tests with good error handling

The tests demonstrate good practices:

  • Error case validation
  • Deep copy verification
  • Nil handling
asset/assetdomain/asset/asset_test.go (1)

98-151: Well-implemented update and clone tests

Good practices observed:

  • Proper verification of updates
  • Use of builder pattern
  • Deep copy validation in Clone
  • Nil handling
asset/assetinfrastructure/assetfs/file_test.go (2)

23-31: LGTM!

The test effectively covers both successful creation and error handling for invalid URLs.


69-101: ⚠️ Potential issue

Verify path traversal protection.

The test should include cases for potential path traversal attacks:

  • File names with "../" sequences
  • Absolute paths
  • Special characters in file names
✅ Verification successful

Let me gather more information about the file path handling in the codebase, particularly focusing on the asset upload implementation.


Based on the gathered information, I can now generate the final response:

Path traversal protection is implemented securely.

The implementation is safe against path traversal attacks because:

  • File paths are constructed using UUID-based directories: assetDir/XX/YYYY.../filename
  • The UUID is validated before path construction (IsValidUUID check)
  • filepath.Join is used for safe path construction
  • The file system root is constrained by the assetDir constant
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for path traversal protection in the codebase
rg -l "filepath\.Clean|path\.Clean" | grep -v "_test.go"

Length of output: 87


Script:

#!/bin/bash
# Check the implementation of getFSObjectPath and related file path handling
ast-grep --pattern 'func getFSObjectPath($$$)'

# Also check the actual File.UploadAsset implementation
ast-grep --pattern 'func ($_ *File) UploadAsset($$$)'

# Look for any path sanitization in the codebase
rg -A 5 "filepath\.|path\." --type go

Length of output: 13962

asset/assetdomain/asset/builder_test.go (1)

209-235: Consider adding test coverage for concurrent builder usage.

The test suite runs tests in parallel but doesn't verify if the builder is safe for concurrent use. Consider adding tests that specifically verify thread safety.

asset/assetinfrastructure/assetmemory/asset_test.go (1)

1-15: LGTM! Well-organized package structure and imports.

The package structure follows Go conventions and the imports are properly organized, reflecting a clean domain-driven design architecture.

Comment on lines +252 to +313
func TestIntegrationRepo_Remove(t *testing.T) {
now := time.Now()
iId1 := id.NewIntegrationID()
i1 := integration.New().ID(iId1).UpdatedAt(now).MustBuild()

tests := []struct {
name string
seeds integration.List
arg *integration.Integration
want integration.List
wantErr error
mockErr bool
}{
{
name: "Saved",
seeds: integration.List{},
arg: i1,
want: integration.List{i1},
wantErr: nil,
},
{
name: "Saved same data",
seeds: integration.List{i1},
arg: i1,
want: integration.List{i1},
wantErr: nil,
},
{
name: "must mock error",
wantErr: errors.New("test"),
mockErr: true,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

r := NewIntegration()
if tc.mockErr {
SetIntegrationError(r, tc.wantErr)
}
ctx := context.Background()
for _, p := range tc.seeds {
err := r.Save(ctx, p.Clone())
if tc.wantErr != nil {
assert.ErrorIs(t, err, tc.wantErr)
return
}
}

err := r.Save(ctx, tc.arg.Clone())
if tc.wantErr != nil {
assert.ErrorIs(t, err, tc.wantErr)
return
}

assert.Equal(t, []*integration.Integration(tc.want), r.(*Integration).data.Values())
})
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical mismatch in test name versus actual method calls.

“TestIntegrationRepo_Remove” never calls the “Remove” method. Instead, it calls “Save” and verifies saved items. This appears to be reversed or a copy/paste mistake, likely preventing “Remove” from being tested as intended.

Below is a possible fix. Adjust the code to call “Remove” instead of “Save” at lines 304–305:

--- a/integration_test.go
+++ b/integration_test.go
 func TestIntegrationRepo_Remove(t *testing.T) {
   ...
   for _, tc := range tests {
     ...
-    err := r.Save(ctx, tc.arg.Clone())
+    err := r.Remove(ctx, tc.arg.ID())
     if tc.wantErr != nil {
       assert.ErrorIs(t, err, tc.wantErr)
       return
     }
     ...
   }
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +315 to +376
func TestIntegrationRepo_Save(t *testing.T) {
now := time.Now()
iId1 := id.NewIntegrationID()
i1 := integration.New().ID(iId1).UpdatedAt(now).MustBuild()

tests := []struct {
name string
seeds integration.List
arg integration.ID
want integration.List
wantErr error
mockErr bool
}{
{
name: "Saved",
seeds: integration.List{},
arg: iId1,
want: integration.List{},
wantErr: rerror.ErrNotFound,
},
{
name: "Saved same data",
seeds: integration.List{i1},
arg: iId1,
want: nil,
wantErr: nil,
},
{
name: "must mock error",
wantErr: errors.New("test"),
mockErr: true,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

r := NewIntegration()
if tc.mockErr {
SetIntegrationError(r, tc.wantErr)
}
ctx := context.Background()
for _, p := range tc.seeds {
err := r.Save(ctx, p.Clone())
if tc.wantErr != nil {
assert.ErrorIs(t, err, tc.wantErr)
return
}
}

err := r.Remove(ctx, tc.arg)
if tc.wantErr != nil {
assert.ErrorIs(t, err, tc.wantErr)
return
}

assert.Equal(t, []*integration.Integration(tc.want), r.(*Integration).data.Values())
})
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Test name and method usage appear reversed.

Similarly, “TestIntegrationRepo_Save” invokes “Remove” (line 367). To properly test the “Save” method, call “Save” instead of “Remove,” or rename the function to match current behavior.

Here is a possible fix to switch from “Remove” to “Save” within “TestIntegrationRepo_Save”:

--- a/integration_test.go
+++ b/integration_test.go
 func TestIntegrationRepo_Save(t *testing.T) {
   ...
   for _, tc := range tests {
     ...
-    err := r.Remove(ctx, tc.arg)
+    // Build the integration object using tc.arg, or reuse one from seeds:
+    integrationObj := integration.New().ID(tc.arg).UpdatedAt(now).MustBuild()
+    err := r.Save(ctx, integrationObj)
     if tc.wantErr != nil {
       assert.ErrorIs(t, err, tc.wantErr)
       return
     }
     ...
   }
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +33 to +35
func TestPublication_AssetPublic(t *testing.T) {
assert.True(t, (&Publication{assetPublic: true}).AssetPublic())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add test case for false asset visibility.

The test only verifies the true case. Add a test for the false case to ensure complete coverage.

Add this test case:

 func TestPublication_AssetPublic(t *testing.T) {
 	assert.True(t, (&Publication{assetPublic: true}).AssetPublic())
+	assert.False(t, (&Publication{assetPublic: false}).AssetPublic())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestPublication_AssetPublic(t *testing.T) {
assert.True(t, (&Publication{assetPublic: true}).AssetPublic())
}
func TestPublication_AssetPublic(t *testing.T) {
assert.True(t, (&Publication{assetPublic: true}).AssetPublic())
assert.False(t, (&Publication{assetPublic: false}).AssetPublic())
}

Comment on lines +126 to +137
func TestThread_Clone(t *testing.T) {
thread := &Thread{
id: NewID(),
workspace: accountdomain.NewWorkspaceID(),
comments: []*Comment{
{id: NewCommentID()},
},
}
assert.Nil(t, (*Thread)(nil).Clone())
assert.Equal(t, thread, thread.Clone())
assert.NotSame(t, thread, thread.Clone())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance clone testing to verify deep copy behavior

The current clone test verifies basic functionality but should also ensure that:

  1. The comments slice is deep copied
  2. Modifying the cloned thread's comments doesn't affect the original
 func TestThread_Clone(t *testing.T) {
+    comment := NewComment(NewCommentID(), operator.OperatorFromUser(NewUserID()), "test")
     thread := &Thread{
         id:        NewID(),
         workspace: accountdomain.NewWorkspaceID(),
-        comments: []*Comment{
-            {id: NewCommentID()},
-        },
+        comments: []*Comment{comment},
     }
     assert.Nil(t, (*Thread)(nil).Clone())
-    assert.Equal(t, thread, thread.Clone())
-    assert.NotSame(t, thread, thread.Clone())
+    clone := thread.Clone()
+    assert.Equal(t, thread, clone)
+    assert.NotSame(t, thread, clone)
+    
+    // Verify deep copy of comments
+    clone.comments[0].content = "modified"
+    assert.NotEqual(t, thread.comments[0].content, clone.comments[0].content)
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +44 to +61
func TestEvent_Save(t *testing.T) {
now := time.Now()
u := user.New().NewID().Email("[email protected]").Name("John").MustBuild()
a := asset.New().NewID().Project(project.NewID()).Size(100).NewUUID().
CreatedByUser(u.ID()).Thread(id.NewThreadID()).MustBuild()
eID1 := event.NewID()
ev := event.New[any]().ID(eID1).Timestamp(now).Type(event.AssetCreate).Operator(operator.OperatorFromUser(u.ID())).Object(a).MustBuild()

r := NewEvent()
ctx := context.Background()
err := r.Save(ctx, ev)
assert.NoError(t, err)
assert.Equal(t, ev, r.(*Event).data.Values()[0])

// already exist
_ = r.Save(ctx, ev)
assert.Equal(t, 1, len(r.(*Event).data.Values()))
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve test robustness and avoid implementation details.

The test has several areas for improvement:

  1. Accessing internal data structure via type assertion (r.(*Event).data) couples the test to implementation details
  2. Missing important error cases
  3. Could benefit from more comprehensive test cases

Consider this improved version:

 func TestEvent_Save(t *testing.T) {
+    tests := []struct {
+        name    string
+        ctx     context.Context
+        event   *event.Event[any]
+        wantErr error
+    }{
+        {
+            name:    "success",
+            ctx:     context.Background(),
+            event:   setupTestData(t).ev,
+            wantErr: nil,
+        },
+        {
+            name:    "duplicate",
+            ctx:     context.Background(),
+            event:   setupTestData(t).ev,
+            wantErr: nil,
+        },
+        {
+            name:    "nil context",
+            ctx:     nil,
+            event:   setupTestData(t).ev,
+            wantErr: rerror.ErrInvalidContext,
+        },
+        {
+            name:    "nil event",
+            ctx:     context.Background(),
+            event:   nil,
+            wantErr: rerror.ErrInvalidArgument,
+        },
+    }

+    for _, tt := range tests {
+        t.Run(tt.name, func(t *testing.T) {
+            r := NewEvent()
+            err := r.Save(tt.ctx, tt.event)
+            assert.Equal(t, tt.wantErr, err)
+            
+            if tt.wantErr == nil {
+                // Verify save was successful by retrieving the event
+                saved, err := r.FindByID(tt.ctx, tt.event.ID())
+                assert.NoError(t, err)
+                assert.Equal(t, tt.event, saved)
+            }
+        })
+    }
 }

Instead of checking internal data structure:

  1. Use FindByID to verify saved state
  2. Add more test cases for error conditions
  3. Follow table-driven test pattern for better maintainability

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +318 to +370
func TestBuilder_LogoUrl(t *testing.T) {
type fields struct {
i *Integration
}
type args struct {
logoURL *url.URL
}
tests := []struct {
name string
fields fields
args args
want *Builder
}{
{
name: "set",
fields: fields{i: &Integration{}},
args: args{logoURL: lo.Must(url.Parse("https://sub.hugo.com/dir?p=1#test"))},
want: &Builder{i: &Integration{logoUrl: lo.Must(url.Parse("https://sub.hugo.com/dir?p=1#test"))}},
},
{
name: "set",
fields: fields{i: &Integration{}},
args: args{logoURL: nil},
want: &Builder{i: &Integration{logoUrl: nil}},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

b := &Builder{
i: tt.fields.i,
}
assert.Equalf(t, tt.want, b.LogoUrl(tt.args.logoURL), "LogoUrl(%v)", tt.args.logoURL)
})
}
}

func TestBuilder_Developer(t *testing.T) {
uId := accountdomain.NewUserID()
type fields struct {
i *Integration
}
type args struct {
developer UserID
}
tests := []struct {
name string
fields fields
args args
want *Builder
}{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance URL validation tests.

The URL tests should verify:

  1. Invalid URL formats
  2. URL sanitization
  3. Security considerations (e.g., blocked schemes)
 func TestBuilder_LogoUrl(t *testing.T) {
 	type fields struct {
 		i *Integration
 	}
 	type args struct {
 		logoURL *url.URL
 	}
 	tests := []struct {
 		name   string
 		fields fields
 		args   args
+		want   *Builder
-		want   *Builder
+		wantErr error
 	}{
 		{
 			name:   "set",
 			fields: fields{i: &Integration{}},
 			args:   args{logoURL: lo.Must(url.Parse("https://sub.hugo.com/dir?p=1#test"))},
 			want:   &Builder{i: &Integration{logoUrl: lo.Must(url.Parse("https://sub.hugo.com/dir?p=1#test"))}},
 		},
+		{
+			name:   "invalid_scheme",
+			fields: fields{i: &Integration{}},
+			args:   args{logoURL: lo.Must(url.Parse("file:///etc/passwd"))},
+			wantErr: ErrInvalidURLScheme,
+		},
+		{
+			name:   "blocked_host",
+			fields: fields{i: &Integration{}},
+			args:   args{logoURL: lo.Must(url.Parse("https://localhost/test"))},
+			wantErr: ErrBlockedHost,
+		},
+		{
+			name:   "sanitize_path",
+			fields: fields{i: &Integration{}},
+			args:   args{logoURL: lo.Must(url.Parse("https://example.com/../secrets"))},
+			want:   &Builder{i: &Integration{logoUrl: lo.Must(url.Parse("https://example.com/secrets"))}},
+		},
 	}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +513 to +519
func TestBuilder_GenerateToken(t *testing.T) {
b := New()
b.GenerateToken()
assert.NotNil(t, b.i.token)
assert.True(t, strings.HasPrefix(b.i.token, "secret_"))
assert.Equal(t, 50, len(b.i.token))
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance token generation tests.

The token generation test should verify:

  1. Token uniqueness across multiple generations
  2. Token entropy/randomness
  3. Token format compliance with security requirements
 func TestBuilder_GenerateToken(t *testing.T) {
-	b := New()
-	b.GenerateToken()
-	assert.NotNil(t, b.i.token)
-	assert.True(t, strings.HasPrefix(b.i.token, "secret_"))
-	assert.Equal(t, 50, len(b.i.token))
+	const iterations = 1000
+	tokens := make(map[string]bool)
+
+	for i := 0; i < iterations; i++ {
+		b := New()
+		b.GenerateToken()
+		token := b.i.token
+
+		// Verify token is not nil and follows format
+		assert.NotNil(t, token)
+		assert.True(t, strings.HasPrefix(token, "secret_"))
+		assert.Equal(t, 50, len(token))
+
+		// Verify uniqueness
+		assert.False(t, tokens[token], "Token collision detected")
+		tokens[token] = true
+
+		// Verify entropy (should contain mix of characters)
+		hasUpper := false
+		hasLower := false
+		hasDigit := false
+		hasSpecial := false
+		for _, c := range token[7:] { // Skip "secret_" prefix
+			switch {
+			case unicode.IsUpper(c):
+				hasUpper = true
+			case unicode.IsLower(c):
+				hasLower = true
+			case unicode.IsDigit(c):
+				hasDigit = true
+			case strings.ContainsRune("!@#$%^&*", c):
+				hasSpecial = true
+			}
+		}
+		assert.True(t, hasUpper && hasLower && hasDigit && hasSpecial,
+			"Token should contain mix of upper, lower, digits, and special chars")
+	}
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +486 to +491
func TestWebhook_Secret(t *testing.T) {
w := Webhook{}
w.SetSecret("xyz")
assert.Equal(t, "xyz", w.secret)
assert.Equal(t, "xyz", w.Secret())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enhance webhook secret security tests.

The secret testing should verify:

  1. Minimum length requirements
  2. Entropy requirements
  3. Secret storage security
 func TestWebhook_Secret(t *testing.T) {
-	w := Webhook{}
-	w.SetSecret("xyz")
-	assert.Equal(t, "xyz", w.secret)
-	assert.Equal(t, "xyz", w.Secret())
+	t.Run("valid_secret", func(t *testing.T) {
+		w := Webhook{}
+		secret := "a_very_long_and_secure_secret_key_with_sufficient_entropy"
+		w.SetSecret(secret)
+		assert.Equal(t, secret, w.Secret())
+	})
+
+	t.Run("short_secret", func(t *testing.T) {
+		w := Webhook{}
+		assert.Panics(t, func() {
+			w.SetSecret("short")
+		})
+	})
+
+	t.Run("weak_secret", func(t *testing.T) {
+		w := Webhook{}
+		assert.Panics(t, func() {
+			w.SetSecret("1234567890123456") // All digits
+		})
+	})
+
+	t.Run("secret_storage", func(t *testing.T) {
+		w := Webhook{}
+		secret := "a_very_long_and_secure_secret_key_with_sufficient_entropy"
+		w.SetSecret(secret)
+		
+		// Verify secret is not stored in plain text
+		assert.NotEqual(t, secret, w.secret)
+		
+		// Verify secret is retrievable
+		assert.Equal(t, secret, w.Secret())
+	})
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +338 to +340
args: args{pid1, usecasex.CursorPagination{First: lo.ToPtr(int64(1))}.Wrap()},
want: []*asset.Asset{a1, a2},
wantErr: nil,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect pagination test assertion.

The test case requests 1 item (First: lo.ToPtr(int64(1))) but expects 2 items []*asset.Asset{a1, a2}. This appears to be a bug in the test case.

Apply this fix:

     args:    args{pid1, usecasex.CursorPagination{First: lo.ToPtr(int64(1))}.Wrap()},
-    want:    []*asset.Asset{a1, a2},
+    want:    []*asset.Asset{a1},
     wantErr: nil,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
args: args{pid1, usecasex.CursorPagination{First: lo.ToPtr(int64(1))}.Wrap()},
want: []*asset.Asset{a1, a2},
wantErr: nil,
args: args{pid1, usecasex.CursorPagination{First: lo.ToPtr(int64(1))}.Wrap()},
want: []*asset.Asset{a1},
wantErr: nil,

Comment on lines +399 to +456
func TestAssetRepo_Delete(t *testing.T) {
pid1 := id.NewProjectID()
id1 := id.NewAssetID()
uid1 := accountdomain.NewUserID()
s := lo.ToPtr(asset.ArchiveExtractionStatusPending)
a1 := asset.New().NewID().Project(pid1).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(uid1).Size(1000).Thread(id.NewThreadID()).MustBuild()
tests := []struct {
name string
seeds []*asset.Asset
arg id.AssetID
wantErr error
}{
{
name: "Found 1",
seeds: []*asset.Asset{
a1,
},
arg: id1,
wantErr: nil,
},
{
name: "Found 2",
seeds: []*asset.Asset{
a1,
asset.New().NewID().Project(id.NewProjectID()).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(accountdomain.NewUserID()).Size(1000).Thread(id.NewThreadID()).MustBuild(),
asset.New().NewID().Project(id.NewProjectID()).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(accountdomain.NewUserID()).Size(1000).Thread(id.NewThreadID()).MustBuild(),
},
arg: id1,
wantErr: nil,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

r := NewAsset()
ctx := context.Background()
for _, a := range tc.seeds {
err := r.Save(ctx, a.Clone())
assert.NoError(t, err)
}

err := r.Delete(ctx, tc.arg)
if tc.wantErr != nil {
assert.ErrorIs(t, err, tc.wantErr)
return
}
assert.NoError(t, err)
_, err = r.FindByID(ctx, tc.arg)
assert.ErrorIs(t, err, rerror.ErrNotFound)
})
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add test case for deleting non-existent asset.

The test suite should include a case for attempting to delete a non-existent asset to verify the behavior in this scenario.

Add this test case:

 tests := []struct {
     name    string
     seeds   []*asset.Asset
     arg     id.AssetID
     wantErr error
 }{
+    {
+        name:    "Not found",
+        seeds:   []*asset.Asset{},
+        arg:     id.NewAssetID(),
+        wantErr: rerror.ErrNotFound,
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestAssetRepo_Delete(t *testing.T) {
pid1 := id.NewProjectID()
id1 := id.NewAssetID()
uid1 := accountdomain.NewUserID()
s := lo.ToPtr(asset.ArchiveExtractionStatusPending)
a1 := asset.New().NewID().Project(pid1).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(uid1).Size(1000).Thread(id.NewThreadID()).MustBuild()
tests := []struct {
name string
seeds []*asset.Asset
arg id.AssetID
wantErr error
}{
{
name: "Found 1",
seeds: []*asset.Asset{
a1,
},
arg: id1,
wantErr: nil,
},
{
name: "Found 2",
seeds: []*asset.Asset{
a1,
asset.New().NewID().Project(id.NewProjectID()).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(accountdomain.NewUserID()).Size(1000).Thread(id.NewThreadID()).MustBuild(),
asset.New().NewID().Project(id.NewProjectID()).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(accountdomain.NewUserID()).Size(1000).Thread(id.NewThreadID()).MustBuild(),
},
arg: id1,
wantErr: nil,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
r := NewAsset()
ctx := context.Background()
for _, a := range tc.seeds {
err := r.Save(ctx, a.Clone())
assert.NoError(t, err)
}
err := r.Delete(ctx, tc.arg)
if tc.wantErr != nil {
assert.ErrorIs(t, err, tc.wantErr)
return
}
assert.NoError(t, err)
_, err = r.FindByID(ctx, tc.arg)
assert.ErrorIs(t, err, rerror.ErrNotFound)
})
}
}
func TestAssetRepo_Delete(t *testing.T) {
pid1 := id.NewProjectID()
id1 := id.NewAssetID()
uid1 := accountdomain.NewUserID()
s := lo.ToPtr(asset.ArchiveExtractionStatusPending)
a1 := asset.New().NewID().Project(pid1).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(uid1).Size(1000).Thread(id.NewThreadID()).MustBuild()
tests := []struct {
name string
seeds []*asset.Asset
arg id.AssetID
wantErr error
}{
{
name: "Not found",
seeds: []*asset.Asset{},
arg: id.NewAssetID(),
wantErr: rerror.ErrNotFound,
},
{
name: "Found 1",
seeds: []*asset.Asset{
a1,
},
arg: id1,
wantErr: nil,
},
{
name: "Found 2",
seeds: []*asset.Asset{
a1,
asset.New().NewID().Project(id.NewProjectID()).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(accountdomain.NewUserID()).Size(1000).Thread(id.NewThreadID()).MustBuild(),
asset.New().NewID().Project(id.NewProjectID()).ArchiveExtractionStatus(s).NewUUID().
CreatedByUser(accountdomain.NewUserID()).Size(1000).Thread(id.NewThreadID()).MustBuild(),
},
arg: id1,
wantErr: nil,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
r := NewAsset()
ctx := context.Background()
for _, a := range tc.seeds {
err := r.Save(ctx, a.Clone())
assert.NoError(t, err)
}
err := r.Delete(ctx, tc.arg)
if tc.wantErr != nil {
assert.ErrorIs(t, err, tc.wantErr)
return
}
assert.NoError(t, err)
_, err = r.FindByID(ctx, tc.arg)
assert.ErrorIs(t, err, rerror.ErrNotFound)
})
}
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
asset/assetinfrastructure/unzip/unzip_test.go (1)

14-18: Use t.TempDir for automatic cleanup

Instead of manually creating and removing the temporary directory, consider using testing’s built-in t.TempDir() function. This approach automatically manages temp directory cleanup and may improve clarity.

- tempDir, err := os.MkdirTemp("", "unzip_test")
- if err != nil {
-     t.Fatalf("Failed to create temp dir: %v", err)
- }
- defer os.RemoveAll(tempDir)
+ tempDir := t.TempDir()
asset/assetinfrastructure/unzip/unzip.go (1)

94-133: Ensure robust file mode handling for extracted files

Verifying that the file modes in the archive are applied correctly is good practice. However, if the files originate from untrusted sources, consider normalizing or limiting the permissions for security reasons.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f1d1ee and 4e3c73b.

📒 Files selected for processing (2)
  • asset/assetinfrastructure/unzip/unzip.go (1 hunks)
  • asset/assetinfrastructure/unzip/unzip_test.go (1 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
asset/assetinfrastructure/unzip/unzip_test.go

6-6: SA1019: "io/ioutil" has been deprecated since Go 1.19: As of Go 1.16, the same functionality is now provided by package [io] or package [os], and those implementations should be preferred in new code. See the specific function documentation for details.

(staticcheck)

import (
"archive/zip"
"bytes"
"io/ioutil"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use “os” or “io” instead of “io/ioutil”

The “io/ioutil” package has been deprecated since Go 1.19. Please replace any usage of “io/ioutil” (including the import on line 6 and the call to “ioutil.WriteFile” on line 90) with equivalent functions in “os” or “io” packages, such as “os.WriteFile”.

Example fix:

-import (
-	"archive/zip"
-	"bytes"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"testing"
-)
+import (
+	"archive/zip"
+	"bytes"
+	"io"
+	"os"
+	"path/filepath"
+	"testing"
+)
...
-	return ioutil.WriteFile(zipFilePath, buf.Bytes(), 0644)
+	return os.WriteFile(zipFilePath, buf.Bytes(), 0644)

Also applies to: 90-90

🧰 Tools
🪛 golangci-lint (1.62.2)

6-6: SA1019: "io/ioutil" has been deprecated since Go 1.19: As of Go 1.16, the same functionality is now provided by package [io] or package [os], and those implementations should be preferred in new code. See the specific function documentation for details.

(staticcheck)

Comment on lines +62 to +79
totalFiles := len(reader.File)
completedFiles := 0

go func() {
defer reader.Close()
defer close(au.progress)

for _, file := range reader.File {
au.wg.Add(1)
au.sem <- struct{}{} // acquire semaphore

go func(f *zip.File) {
defer au.wg.Done()
defer func() { <-au.sem }() // release semaphore

err := au.extractFile(f)
completedFiles++
au.progress <- Progress{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Protect shared counter “completedFiles” in concurrent environment

The variable “completedFiles” is incremented from multiple goroutines without synchronization. This can cause data races or incorrect counts. Consider using an atomic counter or locking mechanism to ensure proper concurrency control.

Example fix using atomic:

 import (
     "archive/zip"
     "io"
     "os"
     "path/filepath"
     "sync"
+    "sync/atomic"
 )
 ...
 var completedFiles int32
 ...
 go func(f *zip.File) {
     defer au.wg.Done()
     defer func() { <-au.sem }()
     
     err := au.extractFile(f)
-    completedFiles++
+    atomic.AddInt32(&completedFiles, 1)

     au.progress <- Progress{
         CurrentFile:    f.Name,
         TotalFiles:     totalFiles,
-        CompletedFiles: completedFiles,
+        CompletedFiles: int(atomic.LoadInt32(&completedFiles)),
         Error:          err,
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants