@@ -15,6 +15,7 @@ import (
1515 "github.com/google/uuid"
1616 "github.com/memodb-io/Acontext/internal/config"
1717 "github.com/memodb-io/Acontext/internal/modules/model"
18+ "github.com/memodb-io/Acontext/internal/modules/repo"
1819 "github.com/memodb-io/Acontext/internal/modules/serializer"
1920 "github.com/memodb-io/Acontext/internal/modules/service"
2021 "github.com/memodb-io/Acontext/internal/pkg/utils/fileparser"
@@ -133,6 +134,40 @@ func (m *MockArtifactService) CreateFromBytes(ctx context.Context, in service.Cr
133134 return args .Get (0 ).(* model.Artifact ), args .Error (1 )
134135}
135136
137+ // MockDiskRepo is a mock implementation of DiskRepo
138+ type MockDiskRepo struct {
139+ mock.Mock
140+ }
141+
142+ func (m * MockDiskRepo ) Create (ctx context.Context , d * model.Disk ) error {
143+ args := m .Called (ctx , d )
144+ return args .Error (0 )
145+ }
146+
147+ func (m * MockDiskRepo ) Delete (ctx context.Context , projectID uuid.UUID , diskID uuid.UUID ) error {
148+ args := m .Called (ctx , projectID , diskID )
149+ return args .Error (0 )
150+ }
151+
152+ func (m * MockDiskRepo ) GetByProjectAndID (ctx context.Context , projectID uuid.UUID , diskID uuid.UUID ) (* model.Disk , error ) {
153+ args := m .Called (ctx , projectID , diskID )
154+ if args .Get (0 ) == nil {
155+ return nil , args .Error (1 )
156+ }
157+ return args .Get (0 ).(* model.Disk ), args .Error (1 )
158+ }
159+
160+ func (m * MockDiskRepo ) ListWithCursor (ctx context.Context , projectID uuid.UUID , userIdentifier string , afterCreatedAt time.Time , afterID uuid.UUID , limit int , timeDesc bool ) ([]* model.Disk , error ) {
161+ args := m .Called (ctx , projectID , userIdentifier , afterCreatedAt , afterID , limit , timeDesc )
162+ if args .Get (0 ) == nil {
163+ return nil , args .Error (1 )
164+ }
165+ return args .Get (0 ).([]* model.Disk ), args .Error (1 )
166+ }
167+
168+ // Verify MockDiskRepo implements repo.DiskRepo
169+ var _ repo.DiskRepo = (* MockDiskRepo )(nil )
170+
136171// createTestConfig creates a test config with default artifact settings
137172func createTestConfig (maxUploadSizeBytes int64 ) * config.Config {
138173 return & config.Config {
@@ -260,7 +295,7 @@ func TestArtifactHandler_UpsertArtifact(t *testing.T) {
260295 tt .mockSetup (mockService , tt .diskID , projectID )
261296
262297 testConfig := createTestConfig (tt .maxUploadSize )
263- handler := NewArtifactHandler (mockService , testConfig , nil , nil )
298+ handler := NewArtifactHandler (mockService , nil , testConfig , nil , nil )
264299
265300 // Create multipart form data
266301 body := & bytes.Buffer {}
@@ -356,7 +391,7 @@ func TestArtifactHandler_DeleteArtifact(t *testing.T) {
356391 tt .mockSetup (mockService , tt .diskID , tt .filePath , projectID )
357392
358393 testConfig := createDefaultTestConfig () // Default 16MB
359- handler := NewArtifactHandler (mockService , testConfig , nil , nil )
394+ handler := NewArtifactHandler (mockService , nil , testConfig , nil , nil )
360395
361396 // Create request with query parameters
362397 req := httptest .NewRequest (http .MethodDelete , fmt .Sprintf ("/disk/%s/artifact?file_path=%s" , tt .diskID , tt .filePath ), nil )
@@ -482,7 +517,7 @@ func TestArtifactHandler_UpdateArtifact(t *testing.T) {
482517 tt .mockSetup (mockService , tt .diskID )
483518
484519 testConfig := createDefaultTestConfig () // Default 16MB
485- handler := NewArtifactHandler (mockService , testConfig , nil , nil )
520+ handler := NewArtifactHandler (mockService , nil , testConfig , nil , nil )
486521
487522 // Create JSON request body
488523 requestBody := map [string ]string {
@@ -629,7 +664,7 @@ func TestArtifactHandler_GetArtifact(t *testing.T) {
629664 tt .mockSetup (mockService , tt .diskID , tt .filePath )
630665
631666 testConfig := createDefaultTestConfig () // Default 16MB
632- handler := NewArtifactHandler (mockService , testConfig , nil , nil )
667+ handler := NewArtifactHandler (mockService , nil , testConfig , nil , nil )
633668
634669 // Create request with query parameters
635670 url := fmt .Sprintf ("/disk/%s/artifact?file_path=%s" , tt .diskID , tt .filePath )
@@ -750,7 +785,7 @@ func TestArtifactHandler_GrepArtifacts(t *testing.T) {
750785 mockSvc := new (MockArtifactService )
751786 tt .setupMock (mockSvc )
752787
753- handler := NewArtifactHandler (mockSvc , createTestConfig (10 * 1024 * 1024 ), nil , nil )
788+ handler := NewArtifactHandler (mockSvc , nil , createTestConfig (10 * 1024 * 1024 ), nil , nil )
754789
755790 w := httptest .NewRecorder ()
756791 c , _ := gin .CreateTestContext (w )
@@ -835,7 +870,7 @@ func TestArtifactHandler_GlobArtifacts(t *testing.T) {
835870 mockSvc := new (MockArtifactService )
836871 tt .setupMock (mockSvc )
837872
838- handler := NewArtifactHandler (mockSvc , createTestConfig (10 * 1024 * 1024 ), nil , nil )
873+ handler := NewArtifactHandler (mockSvc , nil , createTestConfig (10 * 1024 * 1024 ), nil , nil )
839874
840875 w := httptest .NewRecorder ()
841876 c , _ := gin .CreateTestContext (w )
@@ -857,3 +892,74 @@ func TestArtifactHandler_GlobArtifacts(t *testing.T) {
857892 })
858893 }
859894}
895+
896+ func TestArtifactHandler_DownloadArtifact (t * testing.T ) {
897+ gin .SetMode (gin .TestMode )
898+
899+ t .Run ("returns 403 when disk belongs to different project" , func (t * testing.T ) {
900+ mockService := new (MockArtifactService )
901+ mockDiskRepo := new (MockDiskRepo )
902+
903+ projectID := uuid .New ()
904+ diskID := uuid .New ()
905+
906+ // Disk not found for this project
907+ mockDiskRepo .On ("GetByProjectAndID" , mock .Anything , projectID , diskID ).
908+ Return (nil , fmt .Errorf ("record not found" ))
909+
910+ handler := NewArtifactHandler (mockService , mockDiskRepo , createDefaultTestConfig (), nil , nil )
911+
912+ w := httptest .NewRecorder ()
913+ c , _ := gin .CreateTestContext (w )
914+ c .Set ("project" , & model.Project {ID : projectID })
915+ c .Request = httptest .NewRequest ("GET" , "/disk/" + diskID .String ()+ "/artifact/download?file_path=/test/file.txt" , nil )
916+ c .Params = gin.Params {{Key : "disk_id" , Value : diskID .String ()}}
917+
918+ handler .DownloadArtifact (c )
919+
920+ assert .Equal (t , http .StatusForbidden , w .Code )
921+ mockService .AssertNotCalled (t , "GetByPath" )
922+ mockDiskRepo .AssertExpectations (t )
923+ })
924+
925+ t .Run ("succeeds when disk belongs to authenticated project" , func (t * testing.T ) {
926+ mockService := new (MockArtifactService )
927+ mockDiskRepo := new (MockDiskRepo )
928+
929+ projectID := uuid .New ()
930+ diskID := uuid .New ()
931+
932+ // Disk found for this project
933+ mockDiskRepo .On ("GetByProjectAndID" , mock .Anything , projectID , diskID ).
934+ Return (& model.Disk {ID : diskID , ProjectID : projectID }, nil )
935+
936+ artifact := & model.Artifact {
937+ ID : uuid .New (),
938+ DiskID : diskID ,
939+ Path : "/test/" ,
940+ Filename : "file.txt" ,
941+ AssetMeta : datatypes .NewJSONType (model.Asset {
942+ Bucket : "test-bucket" ,
943+ S3Key : "test-key" ,
944+ MIME : "text/plain" ,
945+ }),
946+ }
947+ mockService .On ("GetByPath" , mock .Anything , diskID , "/test/" , "file.txt" ).Return (artifact , nil )
948+ mockService .On ("DownloadRawContent" , mock .Anything , artifact ).Return ([]byte ("content" ), "text/plain" , nil )
949+
950+ handler := NewArtifactHandler (mockService , mockDiskRepo , createDefaultTestConfig (), nil , nil )
951+
952+ w := httptest .NewRecorder ()
953+ c , _ := gin .CreateTestContext (w )
954+ c .Set ("project" , & model.Project {ID : projectID })
955+ c .Request = httptest .NewRequest ("GET" , "/disk/" + diskID .String ()+ "/artifact/download?file_path=/test/file.txt" , nil )
956+ c .Params = gin.Params {{Key : "disk_id" , Value : diskID .String ()}}
957+
958+ handler .DownloadArtifact (c )
959+
960+ assert .Equal (t , http .StatusOK , w .Code )
961+ assert .Equal (t , "content" , w .Body .String ())
962+ mockDiskRepo .AssertExpectations (t )
963+ mockService .AssertExpectations (t )
964+ })
965+ }
0 commit comments