Complete restructure of domain and infra layer

master
Caj Larsson 3 years ago
parent a70e8ef74b
commit 8a0e6b80e3

2
.gitignore vendored

@ -0,0 +1,2 @@
sql.db
bog

@ -10,21 +10,3 @@ Don't worry about access credentials, the datasawmp does authorization
without authentication the old school way: encryption. Pass a password without authentication the old school way: encryption. Pass a password
when you create your data and if you pass the same when you retrieve when you create your data and if you pass the same when you retrieve
it, you get the same bits back. it, you get the same bits back.
## TODO
Alpha
- [x] Concurrent access safety
- [x] Test domain
- [x] Test integration
- [ ] Test application
Beta
- [ ] Path Sanitation and rejection
- [ ] Usage statistics
- [ ] Clean up background process
1.0
- [ ] Rendered Dashboard
- [ ] Upload page helper for 404

@ -1,39 +0,0 @@
package application
import (
"fmt"
"net/http/httptest"
"net/http"
"testing"
"strings"
"time"
"caj-larsson/bog/domain"
"caj-larsson/bog/test/mock"
)
func TestApplication(t *testing.T) {
file_service := domain.NewBogFileService(
mock.NewMockUserAgentRepository(),
mock.NewMockFileRepository(),
1000,
time.Hour,
)
bog := Bog {
router: new(http.ServeMux),
file_service: file_service,
address: "fake",
}
bog.routes()
req := httptest.NewRequest("POST", "/apath", strings.NewReader("testdata"))
req.Header.Add("User-Agent", "testingclient")
w := httptest.NewRecorder()
bog.router.ServeHTTP(w, req)
if (w.Code != 200){
fmt.Printf("%v", w)
t.Error("not ok")
}
}

@ -1,11 +1,11 @@
package domain package namespace
import ( import (
"time" "time"
"errors" "errors"
) )
type UserAgent struct { type Namespace struct {
ID int64 ID int64
Name string Name string
LastSeen time.Time LastSeen time.Time
@ -13,13 +13,6 @@ type UserAgent struct {
FileQuota FileSizeQuota FileQuota FileSizeQuota
} }
type BogFile struct {
UserAgentId int64
Path string
Size int64
CreatedAt time.Time
}
var ( var (
ErrDuplicate = errors.New("record already exists") ErrDuplicate = errors.New("record already exists")
ErrExceedQuota = errors.New("file too large") ErrExceedQuota = errors.New("file too large")

@ -0,0 +1,15 @@
package namespace
import "errors"
var (
ErrNoNamespace = errors.New("that namespace does not exist")
)
type Repository interface{
Create(namespace Namespace) (*Namespace, error)
All() ([]Namespace, error)
GetByName(name string) (*Namespace, error)
Update(id int64, namespace Namespace) (*Namespace, error)
Delete(id int64) error
}

@ -1,9 +1,5 @@
package domain package namespace
import (
"io"
"time"
)
type FileSizeQuota struct { type FileSizeQuota struct {
AllowanceKB int64 AllowanceKB int64
@ -33,26 +29,3 @@ func (f *FileSizeQuota) Remove(size int64) error {
return nil return nil
} }
type BogOutFile interface {
Path() string
Size() int64
Modified() time.Time
io.Reader
io.Seeker
io.Closer
}
type BogInFile interface {
Path() string
Size() int64
Modified() time.Time
io.Writer
io.Seeker
io.Closer
}
type FileReference struct {
Path string
UserAgent string
}

@ -1,13 +1,12 @@
package test package namespace
import ( import (
"testing" "testing"
"caj-larsson/bog/domain"
) )
func TestQuota(t *testing.T) { func TestQuota(t *testing.T) {
quota := domain.FileSizeQuota { 1000, 0 } quota := FileSizeQuota { 1000, 0 }
if !quota.Allows(1000) { if !quota.Allows(1000) {
t.Errorf("It should allow filling completely") t.Errorf("It should allow filling completely")
@ -18,7 +17,7 @@ func TestQuota(t *testing.T) {
} }
func TestQuotaManipulation(t *testing.T) { func TestQuotaManipulation(t *testing.T) {
quota := domain.FileSizeQuota { 1000, 0 } quota := FileSizeQuota { 1000, 0 }
if quota.Add(500) != nil { if quota.Add(500) != nil {
t.Errorf("It should allow adding") t.Errorf("It should allow adding")
@ -32,7 +31,7 @@ func TestQuotaManipulation(t *testing.T) {
t.Errorf("It should allow adding up to the limit") t.Errorf("It should allow adding up to the limit")
} }
if quota.Add(1) != domain.ErrExceedQuota { if quota.Add(1) != ErrExceedQuota {
t.Errorf("It should not allow adding beyond limit") t.Errorf("It should not allow adding beyond limit")
} }
@ -40,7 +39,7 @@ func TestQuotaManipulation(t *testing.T) {
t.Errorf("It should not overtaxed after failure to add") t.Errorf("It should not overtaxed after failure to add")
} }
if quota.Remove(1001) != domain.ErrQuotaInvalid { if quota.Remove(1001) != ErrQuotaInvalid {
t.Errorf("It should not allow reducing further than 0") t.Errorf("It should not allow reducing further than 0")
} }

@ -0,0 +1,97 @@
package dataswamp
import (
"io"
"time"
"strconv"
"strings"
"path"
"path/filepath"
"caj-larsson/bog/dataswamp/namespace"
"caj-larsson/bog/dataswamp/swampfile"
)
type SwampFileService struct {
namespace_repo namespace.Repository
swamp_file_repo swampfile.Repository
default_allowance_bytes int64
default_allowance_duration time.Duration
}
func NewSwampFileService(
namespace_repo namespace.Repository,
swamp_file_repo swampfile.Repository,
da_bytes int64,
da_duration time.Duration,
) SwampFileService {
return SwampFileService {namespace_repo, swamp_file_repo, da_bytes, da_duration}
}
func (s SwampFileService) getOrCreateNs(namespace_in string) *namespace.Namespace{
ns, err := s.namespace_repo.GetByName(namespace_in)
if err == namespace.ErrNotExists {
new_ns := namespace.Namespace {
0,
namespace_in,
time.Now(),
s.default_allowance_duration,
namespace.FileSizeQuota { s.default_allowance_bytes, 0 },
}
created_ns, err := s.namespace_repo.Create(new_ns)
if err != nil {
panic(err)
}
return created_ns
}
if err != nil {
panic(err)
}
return ns
}
func (s SwampFileService) SaveFile(ref swampfile.FileReference, src io.Reader, size int64) error {
ns := s.getOrCreateNs(ref.UserAgent)
if !ns.FileQuota.Allows(size) {
return namespace.ErrExceedQuota
}
f, err := s.swamp_file_repo.Create(ref.Path, strconv.FormatInt(ns.ID, 10))
if err != nil {
return err
}
io.Copy(f, src)
f.Close()
ns.FileQuota.Add(size)
s.namespace_repo.Update(ns.ID, *ns)
return nil
}
func (s SwampFileService) OpenOutFile(ref swampfile.FileReference) (swampfile.SwampOutFile, error) {
ns, err := s.namespace_repo.GetByName(ref.UserAgent)
if err == namespace.ErrNotExists {
return nil, err
}
f, err := s.swamp_file_repo.Open(ref.Path, strconv.FormatInt(ns.ID, 10))
if err != nil {
return nil, err
}
return f, nil
}
func CleanPath(inpath string) string {
return filepath.FromSlash(path.Clean("/" + strings.Trim(inpath, "/")))
}

@ -0,0 +1,102 @@
package dataswamp
import (
"time"
"bytes"
"testing"
"github.com/matryer/is"
"caj-larsson/bog/dataswamp/swampfile"
"caj-larsson/bog/dataswamp/namespace"
m_namespace "caj-larsson/bog/infrastructure/memory/namespace"
m_swampfile "caj-larsson/bog/infrastructure/memory/swampfile"
)
var file_ref1 = swampfile.FileReference { "ptah1", "ua1" }
var file_ref2 = swampfile.FileReference { "path1", "ua2" }
var file_ref3 = swampfile.FileReference { "path2", "ua1" }
func NewTestSwampFileService() SwampFileService {
file_repo := m_swampfile.NewRepository()
ns_repo := m_namespace.NewRepository()
return NewSwampFileService(ns_repo, file_repo, 1024, time.Hour)
}
func TestFileDontExist(t *testing.T) {
s := NewTestSwampFileService()
outfile, err := s.OpenOutFile(file_ref1)
if outfile != nil && err != swampfile.ErrNotExists {
t.Errorf("File shall not exist by default")
}
}
func TestFileIsStored(t *testing.T) {
s := NewTestSwampFileService()
fakefile := bytes.NewBufferString("My bog data")
err := s.SaveFile(file_ref1, fakefile, int64(fakefile.Len()))
if err != nil {
t.Errorf("A small file should be writable %s", err)
}
largefakefile := bytes.NewBufferString("")
for largefakefile.Len() < 64000 {
_, err = largefakefile.WriteString("A very repetitive file")
}
err = s.SaveFile(file_ref3, largefakefile, int64(largefakefile.Len()))
if err != namespace.ErrExceedQuota {
t.Errorf("too large files should not be excepted")
}
}
func TestFileIsReadBack(t *testing.T) {
s := NewTestSwampFileService()
infile := bytes.NewBufferString("My bog data")
_ = s.SaveFile(file_ref1, infile, int64(infile.Len()))
outswampfile, _ := s.OpenOutFile(file_ref1)
outfile := bytes.NewBufferString("")
_, _ = outfile.ReadFrom(outswampfile)
if outfile.String() != "My bog data" {
t.Errorf("file corrupted")
}
}
func TestUAIsolation(t *testing.T) {
s := NewTestSwampFileService()
ns1_file := bytes.NewBufferString("My bog data ua1")
ns2_file := bytes.NewBufferString("My bog data ua2")
_ = s.SaveFile(file_ref1, ns1_file, int64(ns1_file.Len()))
_ = s.SaveFile(file_ref2, ns2_file, int64(ns2_file.Len()))
outswampfile, _ := s.OpenOutFile(file_ref1)
outfile := bytes.NewBufferString("")
_, _ = outfile.ReadFrom(outswampfile)
if outfile.String() != "My bog data ua1" {
t.Errorf("file corrupted")
}
}
func TestCleanPath(t *testing.T) {
is := is.New(t)
is.Equal(CleanPath("/"), "/")
}

@ -0,0 +1,22 @@
package swampfile
import (
"time"
"errors"
)
type SwampFile struct {
UserAgentId int64
Path string
Size int64
CreatedAt time.Time
}
var (
ErrDuplicate = errors.New("record already exists")
ErrExceedQuota = errors.New("file too large")
ErrQuotaInvalid = errors.New("quota invalid")
ErrNotExists = errors.New("row not exists")
ErrUpdateFailed = errors.New("update failed")
ErrDeleteFailed = errors.New("delete failed")
)

@ -1,22 +1,21 @@
package mock package swampfile
import ( import (
"testing" "testing"
"caj-larsson/bog/domain"
) )
func BogFileRepositoryContract(fac func() domain.FileDataRepository, t *testing.T) { func RepositoryContract(fac func() Repository, t *testing.T) {
basicFileOperationContract(fac, t) basicFileOperationContract(fac, t)
} }
func basicFileOperationContract(fac func() domain.FileDataRepository, t *testing.T) { func basicFileOperationContract(fac func() Repository, t *testing.T) {
repo := fac() repo := fac()
not_file, err := repo.Open("doesnot", "exist") not_file, err := repo.Open("doesnot", "exist")
if err != domain.ErrNotExists || not_file != nil{ if err != ErrNotExists || not_file != nil{
t.Errorf("Must raise not exists and file must not open") t.Errorf("Must raise not exists and file must not open")
} }
@ -51,7 +50,7 @@ func basicFileOperationContract(fac func() domain.FileDataRepository, t *testing
deleted_file, err := repo.Open("newfile.new", "ua1") deleted_file, err := repo.Open("newfile.new", "ua1")
if err != domain.ErrNotExists || deleted_file != nil{ if err != ErrNotExists || deleted_file != nil{
t.Errorf("Musn't open deleted files") t.Errorf("Musn't open deleted files")
} }
} }

@ -0,0 +1,7 @@
package swampfile
type Repository interface {
Create(filename string, user_agent_label string) (SwampInFile, error)
Open(filename string, user_agent_label string) (SwampOutFile, error)
Delete(filename string, user_agent_label string)
}

@ -0,0 +1,29 @@
package swampfile
import (
"io"
"time"
)
type SwampOutFile interface {
Path() string
Size() int64
Modified() time.Time
io.Reader
io.Seeker
io.Closer
}
type SwampInFile interface {
Path() string
Size() int64
Modified() time.Time
io.Writer
io.Seeker
io.Closer
}
type FileReference struct {
Path string
UserAgent string
}

@ -1,21 +0,0 @@
package domain
import "errors"
var (
ErrNoUserAgent = errors.New("that useragent does not exist")
)
type UserAgentRepository interface{
Create(useragent UserAgent) (*UserAgent, error)
All() ([]UserAgent, error)
GetByName(name string) (*UserAgent, error)
Update(id int64, useragent UserAgent) (*UserAgent, error)
Delete(id int64) error
}
type FileDataRepository interface {
Create(filename string, user_agent_label string) (BogInFile, error)
Open(filename string, user_agent_label string) (BogOutFile, error)
Delete(filename string, user_agent_label string)
}

@ -1,83 +0,0 @@
package domain
import (
"io"
"time"
"strconv"
)
type BogFileService struct {
user_agent_repo UserAgentRepository
file_data_repo FileDataRepository
default_allowance_bytes int64
default_allowance_duration time.Duration
}
func NewBogFileService(
user_agent_repo UserAgentRepository,
file_data_repo FileDataRepository,
da_bytes int64,
da_duration time.Duration,
) BogFileService {
return BogFileService {user_agent_repo, file_data_repo, da_bytes, da_duration}
}
func (b BogFileService) getOrCreateUA(useragent_in string) *UserAgent{
ua, err := b.user_agent_repo.GetByName(useragent_in)
if err == ErrNotExists {
new_ua := UserAgent {
0, useragent_in, time.Now(), b.default_allowance_duration, FileSizeQuota { b.default_allowance_bytes, 0 },
}
created_ua, err := b.user_agent_repo.Create(new_ua)
if err != nil {
panic(err)
}
return created_ua
}
if err != nil {
panic(err)
}
return ua
}
func (b BogFileService) SaveFile(ref FileReference, src io.Reader, size int64) error {
user_agent := b.getOrCreateUA(ref.UserAgent)
if !user_agent.FileQuota.Allows(size) {
return ErrExceedQuota
}
f, err := b.file_data_repo.Create(ref.Path, strconv.FormatInt(user_agent.ID, 10))
if err != nil {
return err
}
io.Copy(f, src)
f.Close()
user_agent.FileQuota.Add(size)
b.user_agent_repo.Update(user_agent.ID, *user_agent)
return nil
}
func (b BogFileService) OpenOutFile(ref FileReference) (BogOutFile, error) {
user_agent, err := b.user_agent_repo.GetByName(ref.UserAgent)
if err == ErrNotExists {
return nil, err
}
f, err := b.file_data_repo.Open(ref.Path, strconv.FormatInt(user_agent.ID, 10))
if err != nil {
return nil, err
}
return f, nil
}

@ -5,6 +5,7 @@ go 1.18
require ( require (
github.com/BurntSushi/toml v1.1.0 // indirect github.com/BurntSushi/toml v1.1.0 // indirect
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2 // indirect github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2 // indirect
github.com/matryer/is v1.4.0 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect github.com/mattn/go-sqlite3 v1.14.12 // indirect
github.com/spf13/afero v1.8.2 // indirect github.com/spf13/afero v1.8.2 // indirect
golang.org/x/text v0.3.4 // indirect golang.org/x/text v0.3.4 // indirect

@ -125,6 +125,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

@ -0,0 +1,93 @@
package swampfile
import (
"time"
"os"
"path"
"caj-larsson/bog/dataswamp/swampfile"
)
type FileSystemSwampFileData struct {
path string
size int64
mod_time time.Time
file *os.File
}
func (f FileSystemSwampFileData) Read(p []byte) (int, error) {
return f.file.Read(p)
}
func (f FileSystemSwampFileData) Write(p []byte) (int, error) {
return f.file.Write(p)
}
func (f FileSystemSwampFileData) Close() error {
return f.file.Close()
}
func (f FileSystemSwampFileData) Seek(offset int64, whence int) (int64, error) {
return f.file.Seek(offset, whence)
}
func (f FileSystemSwampFileData) Path() string {
return f.path
}
func (f FileSystemSwampFileData) Size() int64 {
return f.size
}
func (f FileSystemSwampFileData) Modified() time.Time{
return time.Now()
}
type Repository struct {
Root string
}
func (f Repository) absPath(filename string, namespace_ns string) string {
return path.Join(f.Root, namespace_ns, filename)
}
func (f Repository) Create(filename string, namespace_ns string) (swampfile.SwampInFile, error) {
abs_path := f.absPath(filename, namespace_ns)
dir := path.Dir(abs_path)
os.MkdirAll(dir, 0750)
file, err := os.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
stat_info, err := file.Stat()
if err != nil {
panic(err)
}
bfd := FileSystemSwampFileData {filename, stat_info.Size(), stat_info.ModTime(), file}
return bfd, nil
}
func (f Repository) Open(filename string, namespace_ns string) (swampfile.SwampOutFile, error) {
abs_path := f.absPath(filename, namespace_ns)
dir := path.Dir(abs_path)
os.MkdirAll(dir, 0750)
file, err := os.OpenFile(abs_path, os.O_RDONLY, 0644)
if err != nil {
return nil, swampfile.ErrNotExists
}
bfd := FileSystemSwampFileData {filename, 0, time.Now(), file}
return bfd, nil
}
func (f Repository) Delete(filename string, namespace_ns string) {
abs_path := f.absPath(filename, namespace_ns)
os.Remove(abs_path)
}

@ -0,0 +1,19 @@
package swampfile
import (
"path"
"testing"
"caj-larsson/bog/dataswamp/swampfile"
)
func TestFsFileRepo(t *testing.T) {
var fac = func() swampfile.Repository {
r := t.TempDir()
d := path.Join(r, "fs")
repo := Repository { d }
return &repo
}
swampfile.RepositoryContract(fac, t)
}

@ -0,0 +1,66 @@
package memory
import (
// "time"
"caj-larsson/bog/dataswamp/namespace"
)
type Repository struct {
IdIdx map[int64] *namespace.Namespace
NameIdx map[string] *namespace.Namespace
NextId int64
}
func NewRepository() *Repository {
r := new(Repository)
r.NextId = 0
r.IdIdx = make(map[int64]*namespace.Namespace)
r.NameIdx = make(map[string]*namespace.Namespace)
return r
}
func (r *Repository) Create(ns namespace.Namespace) (*namespace.Namespace, error) {
r.NextId += 1
ns.ID = r.NextId
r.IdIdx[ns.ID] = &ns
r.NameIdx[ns.Name] = &ns
return &ns, nil
}
func (r *Repository) All() ([]namespace.Namespace, error) {
ns := make([]namespace.Namespace, 0, len(r.IdIdx))
for _, value := range r.IdIdx {
ns = append(ns, *value)
}
return ns, nil
}
func (r *Repository) GetByName(name string) (*namespace.Namespace, error) {
ns, exists := r.NameIdx[name]
if exists {
return ns, nil
}
return nil, namespace.ErrNotExists
}
func (r *Repository) Update(id int64, ns namespace.Namespace) (*namespace.Namespace, error) {
original := *r.IdIdx[id]
ns.ID = id
r.IdIdx[id] = &ns
r.NameIdx[original.Name] = &ns
return &ns, nil
}
func (r *Repository) Delete(id int64) error {
original := *r.IdIdx[id]
delete(r.NameIdx, original.Name)
delete(r.IdIdx, original.ID)
return nil
}

@ -0,0 +1,52 @@
package memory
import (
"testing"
"time"
"caj-larsson/bog/dataswamp/namespace"
)
func TestUserAgentRepo(t *testing.T) {
r := NewRepository()
all, err := r.All()
if len(all) != 0 && err != nil {
t.Errorf("New repo should be empty")
}
ns := namespace.Namespace {23, "n1", time.Now(), time.Duration(time.Hour * 3), namespace.FileSizeQuota {1000, 0} }
ns1, _ := r.Create(ns)
ns.Name = "n2"
ns2, _ := r.Create(ns)
if ns1 == ns2 {
t.Errorf("Must create unique items")
}
all, err = r.All()
if len(all) != 2 && err != nil {
t.Errorf("After adding there should be two Useragent")
}
if ns.ID != 23 {
t.Errorf("It does not change the original UserAgent")
}
ns3, _ := r.GetByName("n2")
if ns3 != ns2 {
t.Errorf("It the correct ns is acquired")
}
if r.Delete(ns2.ID) != nil {
t.Errorf("Must delete without error")
}
all, err = r.All()
if len(all) != 1 && err != nil {
t.Errorf("After deleting one there should be one NS ")
}
}

@ -0,0 +1,90 @@
package swampfile
import (
"time"
"path"
"os"
// "io"
"github.com/spf13/afero"
"caj-larsson/bog/dataswamp/swampfile"
)
type SwampFile struct {
filename string
file afero.File
}
func (f SwampFile) Path() string {
return f.filename
}
func (f SwampFile) Size() int64 {
stat, _ := f.file.Stat()
return int64(stat.Size())
}
func (f SwampFile) Read(p []byte) (int, error) {
return f.file.Read(p)
}
func (f SwampFile) Write(p []byte) (int, error) {
return f.file.Write(p)
}
func (f SwampFile) Close() error {
return f.file.Close()
}
func (f SwampFile) Seek(offset int64, whence int) (int64, error) {
return f.file.Seek(offset, whence)
}
func (f SwampFile) Modified() time.Time {
stat, _ := f.file.Stat()
return stat.ModTime()
}
// The actual repository
type Repository struct {
fs afero.Fs
}
func NewRepository() swampfile.Repository {
return Repository { afero.NewMemMapFs() }
}
func (r Repository) Create(filename string, namespace_stub string) (swampfile.SwampInFile, error) {
abs_path := path.Join(filename, namespace_stub)
dir := path.Dir(abs_path)
r.fs.MkdirAll(dir, 0750)
file, err := r.fs.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
bf := SwampFile {filename, file}
return bf, nil
}
func (r Repository) Open(filename string, namespace_stub string) (swampfile.SwampOutFile, error) {
abs_path := path.Join(filename, namespace_stub)
dir := path.Dir(abs_path)
r.fs.MkdirAll(dir, 0750)
file, err := r.fs.OpenFile(abs_path, os.O_RDONLY, 0644)
if err != nil {
return nil, swampfile.ErrNotExists
}
bf := SwampFile {filename, file}
return bf, nil
}
func (r Repository) Delete(filename string, namespace_stub string) {
abs_path := path.Join(filename, namespace_stub)
r.fs.Remove(abs_path)
}

@ -0,0 +1,11 @@
package swampfile
import (
"testing"
"caj-larsson/bog/dataswamp/swampfile"
)
func TestFileRepo(t *testing.T) {
swampfile.RepositoryContract(NewRepository, t)
}

@ -0,0 +1,45 @@
package namespace
import (
"errors"
"time"
"caj-larsson/bog/dataswamp/namespace"
)
type NamespaceRecord struct {
ID int64
Name string
LastSeen string
AllowanceSeconds int64
QuotaKB int64
QuotaUsedKB int64
}
var ErrUnparseableRecord = errors.New("record could not be mapped to entity")
func (r *NamespaceRecord) toEntity() (*namespace.Namespace, error) {
lastseen, err := time.Parse(time.RFC3339, r.LastSeen)
if err != nil {
return nil, ErrUnparseableRecord
}
var ns = new(namespace.Namespace)
ns.ID = r.ID
ns.Name = r.Name
ns.LastSeen = lastseen
ns.AllowanceDuration = time.Duration(r.AllowanceSeconds * int64(time.Second))
ns.FileQuota = namespace.FileSizeQuota { r.QuotaKB, r.QuotaUsedKB }
return ns, err
}
func fromEntity(ns namespace.Namespace) (*NamespaceRecord, error) {
var record = new(NamespaceRecord)
record.ID = ns.ID
record.Name = ns.Name
record.LastSeen = ns.LastSeen.Format(time.RFC3339)
record.AllowanceSeconds = int64(ns.AllowanceDuration.Seconds())
record.QuotaKB = ns.FileQuota.AllowanceKB
record.QuotaUsedKB = ns.FileQuota.CurrentUsage
return record, nil
}

@ -1,31 +1,31 @@
package integration package namespace
import ( import (
"caj-larsson/bog/domain" "caj-larsson/bog/dataswamp/namespace"
"database/sql" "database/sql"
"errors" "errors"
"github.com/mattn/go-sqlite3" "github.com/mattn/go-sqlite3"
) )
type SQLiteUserAgentRepository struct { type Repository struct {
db *sql.DB db *sql.DB
} }
func NewSQLiteUserAgentRepository(filename string) *SQLiteUserAgentRepository { func NewRepository(filename string) *Repository {
db, err := sql.Open("sqlite3", filename) db, err := sql.Open("sqlite3", filename)
if err != nil { if err != nil {
panic(err) panic(err)
} }
repo := SQLiteUserAgentRepository{ repo := Repository{
db: db, db: db,
} }
repo.migrate() repo.migrate()
return &repo return &repo
} }
func (r SQLiteUserAgentRepository) migrate() error { func (r Repository) migrate() error {
query := ` query := `
CREATE TABLE IF NOT EXISTS useragent( CREATE TABLE IF NOT EXISTS namespace(
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE,
lastseen text, lastseen text,
@ -38,21 +38,21 @@ func (r SQLiteUserAgentRepository) migrate() error {
return err return err
} }
func (r *SQLiteUserAgentRepository) Create(useragent domain.UserAgent) (*domain.UserAgent, error) { func (r *Repository) Create(ns namespace.Namespace) (*namespace.Namespace, error) {
var record, err = fromEntity(useragent) var record, err = fromEntity(ns)
if err != nil { if err != nil {
} }
res, err := r.db.Exec( res, err := r.db.Exec(
"INSERT INTO useragent(name, lastseen, allowance_time, quota_kb, quota_usage_kb) values(?,?,?,?,?)", "INSERT INTO namespace(name, lastseen, allowance_time, quota_kb, quota_usage_kb) values(?,?,?,?,?)",
record.Name, record.LastSeen, record.AllowanceSeconds, record.QuotaKB, record.QuotaUsedKB, record.Name, record.LastSeen, record.AllowanceSeconds, record.QuotaKB, record.QuotaUsedKB,
) )
if err != nil { if err != nil {
var sqliteErr sqlite3.Error var sqliteErr sqlite3.Error
if errors.As(err, &sqliteErr) { if errors.As(err, &sqliteErr) {
if errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) { if errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) {
return nil, domain.ErrDuplicate return nil, namespace.ErrDuplicate
} }
} }
return nil, err return nil, err
@ -62,61 +62,61 @@ func (r *SQLiteUserAgentRepository) Create(useragent domain.UserAgent) (*domain.
if err != nil { if err != nil {
return nil, err return nil, err
} }
useragent.ID = id ns.ID = id
return &useragent, nil return &ns, nil
} }
func (r SQLiteUserAgentRepository) All() ([]domain.UserAgent, error) { func (r Repository) All() ([]namespace.Namespace, error) {
rows, err := r.db.Query("SELECT * FROM useragent") rows, err := r.db.Query("SELECT * FROM namespace")
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var all []domain.UserAgent var all []namespace.Namespace
for rows.Next() { for rows.Next() {
var record UserAgentDBRecord var record NamespaceRecord
if err := rows.Scan(&record.ID, &record.Name, &record.LastSeen, &record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB); err != nil { if err := rows.Scan(&record.ID, &record.Name, &record.LastSeen, &record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB); err != nil {
return nil, err return nil, err
} }
var useragent, err = record.toEntity() var ns, err = record.toEntity()
if err != nil { if err != nil {
return nil, err return nil, err
} }
all = append(all, *useragent) all = append(all, *ns)
} }
return all, nil return all, nil
} }
func (r SQLiteUserAgentRepository) GetByName(name string) (*domain.UserAgent, error) { func (r Repository) GetByName(name string) (*namespace.Namespace, error) {
row := r.db.QueryRow("SELECT id, name, lastseen, allowance_time, quota_kb, quota_usage_kb FROM useragent WHERE name = ?", name) row := r.db.QueryRow("SELECT id, name, lastseen, allowance_time, quota_kb, quota_usage_kb FROM namespace WHERE name = ?", name)
var record UserAgentDBRecord var record NamespaceRecord
if err := row.Scan(&record.ID, &record.Name, &record.LastSeen, &record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB); err != nil { if err := row.Scan(&record.ID, &record.Name, &record.LastSeen, &record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB); err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, domain.ErrNotExists return nil, namespace.ErrNotExists
} }
return nil, err return nil, err
} }
var useragent, err = record.toEntity() var ns, err = record.toEntity()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return useragent, nil return ns, nil
} }
func (r SQLiteUserAgentRepository) Update(id int64, updated domain.UserAgent) (*domain.UserAgent, error) { func (r Repository) Update(id int64, updated namespace.Namespace) (*namespace.Namespace, error) {
if id == 0 { if id == 0 {
return nil, errors.New("invalid updated ID") return nil, errors.New("invalid updated ID")
} }
var record, err = fromEntity(updated) var record, err = fromEntity(updated)
res, err := r.db.Exec("UPDATE useragent SET name = ?, lastseen = ?, allowance_time = ?, quota_kb = ?, quota_usage_kb = ? WHERE id = ?", res, err := r.db.Exec("UPDATE namespace SET name = ?, lastseen = ?, allowance_time = ?, quota_kb = ?, quota_usage_kb = ? WHERE id = ?",
record.Name, record.LastSeen, record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB, id) record.Name, record.LastSeen, record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB, id)
if err != nil { if err != nil {
@ -129,14 +129,14 @@ func (r SQLiteUserAgentRepository) Update(id int64, updated domain.UserAgent) (*
} }
if rowsAffected == 0 { if rowsAffected == 0 {
return nil, domain.ErrUpdateFailed return nil, namespace.ErrUpdateFailed
} }
return &updated, nil return &updated, nil
} }
func (r SQLiteUserAgentRepository) Delete(id int64) error { func (r Repository) Delete(id int64) error {
res, err := r.db.Exec("DELETE FROM useragent WHERE id = ?", id) res, err := r.db.Exec("DELETE FROM namespace WHERE id = ?", id)
if err != nil { if err != nil {
return err return err
} }
@ -147,7 +147,7 @@ func (r SQLiteUserAgentRepository) Delete(id int64) error {
} }
if rowsAffected == 0 { if rowsAffected == 0 {
return domain.ErrDeleteFailed return namespace.ErrDeleteFailed
} }
return err return err

@ -1,94 +0,0 @@
package integration
import (
"time"
"os"
"path"
"caj-larsson/bog/domain"
)
type FileSystemBogFileData struct {
path string
size int64
mod_time time.Time
file *os.File
}
func (f FileSystemBogFileData) Read(p []byte) (int, error) {
return f.file.Read(p)
}
func (f FileSystemBogFileData) Write(p []byte) (int, error) {
return f.file.Write(p)
}
func (f FileSystemBogFileData) Close() error {
return f.file.Close()
}
func (f FileSystemBogFileData) Seek(offset int64, whence int) (int64, error) {
return f.file.Seek(offset, whence)
}
func (f FileSystemBogFileData) Path() string {
return f.path
}
func (f FileSystemBogFileData) Size() int64 {
return f.size
}
func (f FileSystemBogFileData) Modified() time.Time{
return time.Now()
}
type FileSystemBogRepository struct {
Root string
}
func (f FileSystemBogRepository) absPath(filename string, user_agent_label string) string {
return path.Join(f.Root, user_agent_label, filename)
}
func (f FileSystemBogRepository) Create(filename string, user_agent_label string) (domain.BogInFile, error) {
abs_path := f.absPath(filename, user_agent_label)
dir := path.Dir(abs_path)
os.MkdirAll(dir, 0750)
file, err := os.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
stat_info, err := file.Stat()
if err != nil {
panic(err)
}
bfd := FileSystemBogFileData {filename, stat_info.Size(), stat_info.ModTime(), file}
return bfd, nil
}
func (f FileSystemBogRepository) Open(filename string, user_agent_label string) (domain.BogOutFile, error) {
abs_path := f.absPath(filename, user_agent_label)
dir := path.Dir(abs_path)
os.MkdirAll(dir, 0750)
file, err := os.OpenFile(abs_path, os.O_RDONLY, 0644)
if err != nil {
return nil, domain.ErrNotExists
}
bfd := FileSystemBogFileData {filename, 0, time.Now(), file}
return bfd, nil
}
func (f FileSystemBogRepository) Delete(filename string, user_agent_label string) {
abs_path := f.absPath(filename, user_agent_label)
os.Remove(abs_path)
}

@ -1,45 +0,0 @@
package integration
import (
"errors"
"time"
"caj-larsson/bog/domain"
)
type UserAgentDBRecord struct {
ID int64
Name string
LastSeen string
AllowanceSeconds int64
QuotaKB int64
QuotaUsedKB int64
}
var ErrUnparseableRecord = errors.New("record could not be mapped to entity")
func (r *UserAgentDBRecord) toEntity() (*domain.UserAgent, error) {
lastseen, err := time.Parse(time.RFC3339, r.LastSeen)
if err != nil {
return nil, ErrUnparseableRecord
}
var useragent = new(domain.UserAgent)
useragent.ID = r.ID
useragent.Name = r.Name
useragent.LastSeen = lastseen
useragent.AllowanceDuration = time.Duration(r.AllowanceSeconds * int64(time.Second))
useragent.FileQuota = domain.FileSizeQuota { r.QuotaKB, r.QuotaUsedKB }
return useragent, err
}
func fromEntity(useragent domain.UserAgent) (*UserAgentDBRecord, error) {
var record = new(UserAgentDBRecord)
record.ID = useragent.ID
record.Name = useragent.Name
record.LastSeen = useragent.LastSeen.Format(time.RFC3339)
record.AllowanceSeconds = int64(useragent.AllowanceDuration.Seconds())
record.QuotaKB = useragent.FileQuota.AllowanceKB
record.QuotaUsedKB = useragent.FileQuota.CurrentUsage
return record, nil
}

@ -1,7 +1,7 @@
package main package main
import ( import (
"caj-larsson/bog/application" "caj-larsson/bog/server"
"io/ioutil" "io/ioutil"
"fmt" "fmt"
) )
@ -10,16 +10,16 @@ func main() {
content, err := ioutil.ReadFile("default.toml") content, err := ioutil.ReadFile("default.toml")
if err != nil { if err != nil {
panic(err) panic(err)
} }
config, err := application.ConfigFromToml(string(content)) config, err := server.ConfigFromToml(string(content))
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Printf("%+v\n", config) fmt.Printf("Dataswamp running")
bog := application.New(config) bog := server.New(config)
bog.Run() bog.Run()
} }

@ -1,12 +1,15 @@
package application package server
import ( import (
"net/http" "net/http"
"fmt" "fmt"
"strconv" "strconv"
// "io" // "io"
"caj-larsson/bog/domain" "caj-larsson/bog/dataswamp"
"caj-larsson/bog/integration" "caj-larsson/bog/dataswamp/namespace"
"caj-larsson/bog/dataswamp/swampfile"
sql_namespace "caj-larsson/bog/infrastructure/sqlite/namespace"
fs_swampfile "caj-larsson/bog/infrastructure/fs/swampfile"
) )
type Router interface { type Router interface {
@ -16,21 +19,21 @@ type Router interface {
type Bog struct { type Bog struct {
router Router router Router
file_service domain.BogFileService file_service dataswamp.SwampFileService
address string address string
} }
func buildFileDataRepository(config FileConfig) domain.FileDataRepository{ func buildFileDataRepository(config FileConfig) swampfile.Repository{
fsBogRepo := new(integration.FileSystemBogRepository) fsSwampfileRepo := new(fs_swampfile.Repository)
fsBogRepo.Root = config.Path fsSwampfileRepo.Root = config.Path
return fsBogRepo return fsSwampfileRepo
} }
func buildUserAgentRepository(config DatabaseConfig) *integration.SQLiteUserAgentRepository{ func buildUserAgentRepository(config DatabaseConfig) namespace.Repository{
if config.Backend != "sqlite" { if config.Backend != "sqlite" {
panic("Can only handle sqlite") panic("Can only handle sqlite")
} }
return integration.NewSQLiteUserAgentRepository(config.Connection) return sql_namespace.NewRepository(config.Connection)
} }
func (b *Bog) fileHandler(w http.ResponseWriter, r *http.Request) { func (b *Bog) fileHandler(w http.ResponseWriter, r *http.Request) {
@ -39,13 +42,13 @@ func (b *Bog) fileHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
ref := domain.FileReference {r.URL.Path, r.Header["User-Agent"][0]} ref := swampfile.FileReference {r.URL.Path, r.Header["User-Agent"][0]}
switch r.Method { switch r.Method {
case "GET": case "GET":
bog_file, err := b.file_service.OpenOutFile(ref) swamp_file, err := b.file_service.OpenOutFile(ref)
if err == domain.ErrNotExists { if err == swampfile.ErrNotExists {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
@ -54,7 +57,7 @@ func (b *Bog) fileHandler(w http.ResponseWriter, r *http.Request) {
panic(err) panic(err)
} }
http.ServeContent(w, r, bog_file.Path(), bog_file.Modified(), bog_file) http.ServeContent(w, r, swamp_file.Path(), swamp_file.Modified(), swamp_file)
case "POST": case "POST":
fallthrough fallthrough
case "PUT": case "PUT":
@ -84,11 +87,11 @@ func New(config *Configuration) *Bog {
b := new(Bog) b := new(Bog)
b.address = config.bindAddress() b.address = config.bindAddress()
fsBogRepo := buildFileDataRepository(config.File) fsSwampRepo := buildFileDataRepository(config.File)
uaRepo := buildUserAgentRepository(config.Database) uaRepo := buildUserAgentRepository(config.Database)
b.file_service = domain.NewBogFileService( b.file_service = dataswamp.NewSwampFileService(
uaRepo, fsBogRepo, config.Quota.ParsedSizeBytes(), config.Quota.ParsedDuration(), uaRepo, fsSwampRepo, config.Quota.ParsedSizeBytes(), config.Quota.ParsedDuration(),
) )
b.router = new(http.ServeMux) b.router = new(http.ServeMux)

@ -0,0 +1,40 @@
package server
import (
"testing"
// "fmt"
// "net/http/httptest"
// "net/http"
// "strings"
// "time"
// "caj-larsson/bog/domain_dataswamp"
)
func TestApplication(t *testing.T) {
// file_service := domain_dataswamp.NewBogFileService(
// mock.NewMockUserAgentRepository(),
// mock.NewMockFileRepository(),
// 1000,
// time.Hour,
// )
// bog := Bog {
// router: new(http.ServeMux),
// file_service: file_service,
// address: "fake",
// }
// bog.routes()
// req := httptest.NewRequest("POST", "/apath", strings.NewReader("testdata"))
// req.Header.Add("User-Agent", "testingclient")
// req.Header.Add("Content-Length", "8")
// w := httptest.NewRecorder()
// bog.router.ServeHTTP(w, req)
// if (w.Code != 200){
// fmt.Printf("%v", w)
// t.Error("not ok")
// }
}

@ -1,4 +1,4 @@
package application package server
import ( import (
"fmt" "fmt"

@ -1,4 +1,4 @@
package application package server
import ( import (

@ -1,94 +0,0 @@
package test
import (
"time"
"bytes"
"testing"
"caj-larsson/bog/domain"
"caj-larsson/bog/test/mock"
)
var file_ref1 = domain.FileReference { "path1", "ua1" }
var file_ref2 = domain.FileReference { "path1", "ua2" }
var file_ref3 = domain.FileReference { "path2", "ua1" }
func NewTestBogFileService() domain.BogFileService {
file_repo := mock.NewMockFileRepository()
ua_repo := mock.NewMockUserAgentRepository()
return domain.NewBogFileService(ua_repo, file_repo, 1024, time.Hour)
}
func TestFileDontExist(t *testing.T) {
s := NewTestBogFileService()
outfile, err := s.OpenOutFile(file_ref1)
if outfile != nil && err != domain.ErrNotExists {
t.Errorf("File shall not exist by default")
}
}
func TestFileIsStored(t *testing.T) {
s := NewTestBogFileService()
fakefile := bytes.NewBufferString("My bog data")
err := s.SaveFile(file_ref1, fakefile, int64(fakefile.Len()))
if err != nil {
t.Errorf("A small file should be writable %s", err)
}
largefakefile := bytes.NewBufferString("")
for largefakefile.Len() < 64000 {
_, err = largefakefile.WriteString("A very repetitive file")
}
err = s.SaveFile(file_ref3, largefakefile, int64(largefakefile.Len()))
if err != domain.ErrExceedQuota {
t.Errorf("too large files should not be excepted")
}
}
func TestFileIsReadBack(t *testing.T) {
s := NewTestBogFileService()
infile := bytes.NewBufferString("My bog data")
_ = s.SaveFile(file_ref1, infile, int64(infile.Len()))
outbogfile, _ := s.OpenOutFile(file_ref1)
outfile := bytes.NewBufferString("")
_, _ = outfile.ReadFrom(outbogfile)
if outfile.String() != "My bog data" {
t.Errorf("file corrupted")
}
}
func TestUAIsolation(t *testing.T) {
file_repo := mock.NewMockFileRepository()
ua_repo := mock.NewMockUserAgentRepository()
s := domain.NewBogFileService(ua_repo, file_repo, 1024, time.Hour)
ua1_file := bytes.NewBufferString("My bog data ua1")
ua2_file := bytes.NewBufferString("My bog data ua2")
_ = s.SaveFile(file_ref1, ua1_file, int64(ua1_file.Len()))
_ = s.SaveFile(file_ref2, ua2_file, int64(ua2_file.Len()))
outbogfile, _ := s.OpenOutFile(file_ref1)
outfile := bytes.NewBufferString("")
_, _ = outfile.ReadFrom(outbogfile)
if outfile.String() != "My bog data ua1" {
t.Errorf("file corrupted")
}
}

@ -1,21 +0,0 @@
package test
import (
"path"
"testing"
"caj-larsson/bog/domain"
"caj-larsson/bog/integration"
"caj-larsson/bog/test/mock"
)
func TestFsFileRepo(t *testing.T) {
var fac = func()domain.FileDataRepository {
r := t.TempDir()
d := path.Join(r, "fs")
repo := integration.FileSystemBogRepository { d }
return &repo
}
mock.BogFileRepositoryContract(fac, t)
}

@ -1,90 +0,0 @@
package mock
import (
"time"
"path"
"os"
// "io"
"github.com/spf13/afero"
"caj-larsson/bog/domain"
)
type MockBogFile struct {
filename string
file afero.File
}
func (f MockBogFile) Path() string {
return f.filename
}
func (f MockBogFile) Size() int64 {
stat, _ := f.file.Stat()
return int64(stat.Size())
}
func (f MockBogFile) Read(p []byte) (int, error) {
return f.file.Read(p)
}
func (f MockBogFile) Write(p []byte) (int, error) {
return f.file.Write(p)
}
func (f MockBogFile) Close() error {
return f.file.Close()
}
func (f MockBogFile) Seek(offset int64, whence int) (int64, error) {
return f.file.Seek(offset, whence)
}
func (f MockBogFile) Modified() time.Time {
stat, _ := f.file.Stat()
return stat.ModTime()
}
// The actual repository
type MockFileRepository struct {
fs afero.Fs
}
func NewMockFileRepository() domain.FileDataRepository {
return MockFileRepository { afero.NewMemMapFs() }
}
func (r MockFileRepository) Create(filename string, user_agent_label string) (domain.BogInFile, error) {
abs_path := path.Join( filename, user_agent_label)
dir := path.Dir(abs_path)
r.fs.MkdirAll(dir, 0750)
file, err := r.fs.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
bf := MockBogFile {filename, file}
return bf, nil
}
func (r MockFileRepository) Open(filename string, user_agent_label string) (domain.BogOutFile, error) {
abs_path := path.Join(filename, user_agent_label)
dir := path.Dir(abs_path)
r.fs.MkdirAll(dir, 0750)
file, err := r.fs.OpenFile(abs_path, os.O_RDONLY, 0644)
if err != nil {
return nil, domain.ErrNotExists
}
bf := MockBogFile {filename, file}
return bf, nil
}
func (r MockFileRepository) Delete(filename string, user_agent_label string) {
abs_path := path.Join(filename, user_agent_label)
r.fs.Remove(abs_path)
}

@ -1,11 +0,0 @@
package mock
import (
"testing"
//"caj-larsson/bog/domain"
)
func TestMockFileRepo(t *testing.T) {
BogFileRepositoryContract(NewMockFileRepository, t)
}

@ -1,66 +0,0 @@
package mock
import (
// "time"
"caj-larsson/bog/domain"
)
type MockUserAgentRepository struct {
IdIdx map[int64]*domain.UserAgent
NameIdx map[string]*domain.UserAgent
NextId int64
}
func NewMockUserAgentRepository() *MockUserAgentRepository {
r := new(MockUserAgentRepository)
r.NextId = 0
r.IdIdx = make(map[int64]*domain.UserAgent)
r.NameIdx = make(map[string]*domain.UserAgent)
return r
}
func (r *MockUserAgentRepository) Create(useragent domain.UserAgent) (*domain.UserAgent, error) {
r.NextId += 1
useragent.ID = r.NextId
r.IdIdx[useragent.ID] = &useragent
r.NameIdx[useragent.Name] = &useragent
return &useragent, nil
}
func (r *MockUserAgentRepository) All() ([]domain.UserAgent, error) {
v := make([]domain.UserAgent, 0, len(r.IdIdx))
for _, value := range r.IdIdx {
v = append(v, *value)
}
return v, nil
}
func (r *MockUserAgentRepository) GetByName(name string) (*domain.UserAgent, error) {
useragent, exists := r.NameIdx[name]
if exists {
return useragent, nil
}
return nil, domain.ErrNotExists
}
func (r *MockUserAgentRepository) Update(id int64, useragent domain.UserAgent) (*domain.UserAgent, error) {
original := *r.IdIdx[id]
useragent.ID = id
r.IdIdx[id] = &useragent
r.NameIdx[original.Name] = &useragent
return &useragent, nil
}
func (r *MockUserAgentRepository) Delete(id int64) error {
original := *r.IdIdx[id]
delete(r.NameIdx, original.Name)
delete(r.IdIdx, original.ID)
return nil
}

@ -1,52 +0,0 @@
package mock
import (
"testing"
"time"
"caj-larsson/bog/domain"
)
func TestMockUserAgentRepo(t *testing.T) {
r := NewMockUserAgentRepository()
all, err := r.All()
if len(all) != 0 && err != nil {
t.Errorf("New repo should be empty")
}
ua := domain.UserAgent {23, "n1", time.Now(), time.Duration(time.Hour * 3), domain.FileSizeQuota {1000, 0} }
ua1, _ := r.Create(ua)
ua.Name = "n2"
ua2, _ := r.Create(ua)
if ua1 == ua2 {
t.Errorf("Must create unique items")
}
all, err = r.All()
if len(all) != 2 && err != nil {
t.Errorf("After adding there should be two Useragent")
}
if ua.ID != 23 {
t.Errorf("It does not change the original UserAgent")
}
ua3, _ := r.GetByName("n2")
if ua3 != ua2 {
t.Errorf("It the correct ua is acquired")
}
if r.Delete(ua2.ID) != nil {
t.Errorf("Must delete without error")
}
all, err = r.All()
if len(all) != 1 && err != nil {
t.Errorf("After deleting one there should be one UA ")
}
}
Loading…
Cancel
Save