Skip to content

Feat/add fake low level file upload client #50

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

Merged
merged 1 commit into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ issues:
- path: internal/view/(.+)_test\.go
linters:
- testpackage
- path: fake_(.+)\.go
linters:
- revive
- goconst
- wrapcheck
include:
- EXC0012
- EXC0014
137 changes: 137 additions & 0 deletions internal/fileupload/lowlevel/fake_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package lowlevel

import (
"context"
"fmt"
"io"

"github.com/google/uuid"
)

type LoadedFile struct {
Path string
Content string
}

// revisionState holds the in-memory state for a single revision.
type revisionState struct {
orgID OrgID
sealed bool
files []LoadedFile
}

// FakeSealableClient is a mock implementation of the SealableClient for testing.
// It tracks revisions in memory and enforces the revision lifecycle (create -> upload -> seal).
type FakeSealableClient struct {
cfg FakeClientConfig
revisions map[RevisionID]*revisionState
}

type FakeClientConfig struct {
Limits
}

// NewFakeSealableClient creates a new instance of the fake client.
func NewFakeSealableClient(cfg FakeClientConfig) *FakeSealableClient {
return &FakeSealableClient{
cfg: cfg,
revisions: make(map[RevisionID]*revisionState),
}
}

func (f *FakeSealableClient) CreateRevision(_ context.Context, orgID OrgID) (*UploadRevisionResponseBody, error) {
newRevisionID := uuid.New()
f.revisions[newRevisionID] = &revisionState{
orgID: orgID,
sealed: false,
}

return &UploadRevisionResponseBody{
Data: UploadRevisionResponseData{
ID: newRevisionID,
},
}, nil
}

func (f *FakeSealableClient) UploadFiles(_ context.Context, orgID OrgID, revisionID RevisionID, files []UploadFile) error {
rev, ok := f.revisions[revisionID]
if !ok {
return fmt.Errorf("revision %s not found", revisionID)
}

if rev.orgID != orgID {
return fmt.Errorf("orgID mismatch for revision %s", revisionID)
}

if rev.sealed {
return fmt.Errorf("revision %s is sealed and cannot be modified", revisionID)
}

if len(files) > f.cfg.FileCountLimit {
return NewFileCountLimitError(len(files), f.cfg.FileCountLimit)
}

if len(files) == 0 {
return ErrNoFilesProvided
}

for _, file := range files {
fileInfo, err := file.File.Stat()
if err != nil {
return NewFileAccessError(file.Path, err)
}

if fileInfo.IsDir() {
return NewDirectoryError(file.Path)
}

if fileInfo.Size() > f.cfg.FileSizeLimit {
return NewFileSizeLimitError(file.Path, fileInfo.Size(), f.cfg.FileSizeLimit)
}
}

for _, file := range files {
bts, err := io.ReadAll(file.File)
if err != nil {
return err
}
rev.files = append(rev.files, LoadedFile{
Path: file.Path,
Content: string(bts),
})
}
return nil
}

func (f *FakeSealableClient) SealRevision(_ context.Context, orgID OrgID, revisionID RevisionID) (*SealUploadRevisionResponseBody, error) {
rev, ok := f.revisions[revisionID]
if !ok {
return nil, fmt.Errorf("revision %s not found", revisionID)
}

if rev.orgID != orgID {
return nil, fmt.Errorf("orgID mismatch for revision %s", revisionID)
}

rev.sealed = true
return &SealUploadRevisionResponseBody{}, nil
}

// GetSealedRevisionFiles is a test helper to retrieve files for a sealed revision.
// It is not part of the SealableClient interface.
func (f *FakeSealableClient) GetSealedRevisionFiles(revisionID RevisionID) ([]LoadedFile, error) {
rev, ok := f.revisions[revisionID]
if !ok {
return nil, fmt.Errorf("revision %s not found", revisionID)
}

if !rev.sealed {
return nil, fmt.Errorf("revision %s is not sealed", revisionID)
}

return rev.files, nil
}

func (f *FakeSealableClient) GetLimits() Limits {
return f.cfg.Limits
}