diff --git a/api/handler/settings.go b/api/handler/settings.go new file mode 100644 index 0000000..10919ac --- /dev/null +++ b/api/handler/settings.go @@ -0,0 +1,36 @@ +package handler + +import ( + "encoding/json" + "github.com/wittano/komputer/pkgs/settings" + "io" + "net/http" +) + +func UpdateSettings(w http.ResponseWriter, r *http.Request) error { + defer r.Body.Close() + + rawBody, err := io.ReadAll(r.Body) + if err != nil { + return err + } + + var newSetting settings.Settings + if err = json.Unmarshal(rawBody, &newSetting); err != nil { + return err + } + + return settings.Config.Update(newSetting) +} + +func GetSettings(w http.ResponseWriter, _ *http.Request) error { + data, err := json.Marshal(settings.Config) + if err != nil { + return err + } + + w.WriteHeader(http.StatusOK) + _, err = w.Write(data) + + return err +} diff --git a/cmd/web/main.go b/cmd/web/main.go index c0c211a..0c9941a 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -1,14 +1,25 @@ package main import ( + "flag" "github.com/wittano/komputer/api/handler" + "github.com/wittano/komputer/pkgs/settings" "log" "net/http" ) func main() { + configPath := flag.String("config", settings.DefaultSettingsPath, "Path to web console configuration file") + flag.Parse() + + if err := settings.Load(*configPath); err != nil { + log.Fatal(err) + } + v1 := http.NewServeMux() - v1.HandleFunc("POST /v1/audio", handler.MakeHttpHandler(handler.UploadNewAudio)) + v1.HandleFunc("POST /api/v1/audio", handler.MakeHttpHandler(handler.UploadNewAudio)) + v1.HandleFunc("GET /api/v1/setting", handler.MakeHttpHandler(handler.GetSettings)) + v1.HandleFunc("PUT /api/v1/setting", handler.MakeHttpHandler(handler.UpdateSettings)) server := http.Server{ Addr: ":8080", @@ -16,5 +27,6 @@ func main() { } defer server.Close() + log.Println("Server listening on 8080") log.Fatal(server.ListenAndServe()) } diff --git a/go.mod b/go.mod index eac2522..685f3e2 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,11 @@ require ( github.com/google/uuid v1.6.0 github.com/jarcoal/httpmock v1.3.1 github.com/joho/godotenv v1.5.1 + github.com/mitchellh/go-homedir v1.1.0 github.com/testcontainers/testcontainers-go v0.29.1 github.com/testcontainers/testcontainers-go/modules/mongodb v0.29.1 go.mongodb.org/mongo-driver v1.14.0 + gopkg.in/yaml.v3 v3.0.1 layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32 ) @@ -36,6 +38,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/klauspost/compress v1.17.7 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/patternmatcher v0.6.0 // indirect diff --git a/go.sum b/go.sum index 27ce612..757b37a 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY= -github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -20,6 +18,7 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -69,12 +68,18 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -97,6 +102,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -159,8 +166,6 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= @@ -176,16 +181,12 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -202,8 +203,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -239,6 +238,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkgs/settings/types.go b/pkgs/settings/types.go new file mode 100644 index 0000000..748f037 --- /dev/null +++ b/pkgs/settings/types.go @@ -0,0 +1,142 @@ +package settings + +import ( + "errors" + "github.com/mitchellh/go-homedir" + "gopkg.in/yaml.v3" + "os" + "path/filepath" + "sync" +) + +const ( + DefaultAssertDir = ".cache/komputer" + DefaultSettingsPath = ".config/komputer/settings.yml" +) + +const maxFileSize = 8 * (1 << 20) // 8MB in bytes + +type UploadSettings struct { + MaxFileCount uint `yaml:"max_file_count" json:"max_file_count"` + MaxFileSize uint `yaml:"max_file_size" json:"max_file_size"` +} + +type Settings struct { + AssetDir string `yaml:"asset_dir" json:"asset_dir"` + Upload UploadSettings `yaml:"upload" json:"upload"` +} + +func (s *Settings) Update(new Settings) error { + if new.AssetDir != "" && s.AssetDir != new.AssetDir { + if err := os.MkdirAll(new.AssetDir, 0700); err != nil { + return err + } + + err := moveAssets(s.AssetDir, new.AssetDir) + if err != nil { + return err + } + + s.AssetDir = new.AssetDir + } + + if new.Upload.MaxFileCount != 0 && s.Upload.MaxFileCount != new.Upload.MaxFileCount { + s.Upload.MaxFileCount = new.Upload.MaxFileCount + } + + if new.Upload.MaxFileSize != 0 && s.Upload.MaxFileSize != new.Upload.MaxFileSize { + s.Upload.MaxFileSize = new.Upload.MaxFileSize + } + + return nil +} + +var Config *Settings + +func Load(path string) error { + if Config != nil { + return nil + } + + home, err := homedir.Dir() + if err != nil { + return err + } + + settingPath := filepath.Join(home, DefaultSettingsPath) + if path != "" { + settingPath = filepath.Join(path) + } + + if _, err := os.Stat(settingPath); errors.Is(err, os.ErrNotExist) { + Config, err = defaultSettings(settingPath) + return err + } + + f, err := os.Open(settingPath) + if err != nil { + return err + } + defer f.Close() + + d := yaml.NewDecoder(f) + if err = d.Decode(&Config); err != nil { + return err + } + + return nil +} + +func defaultSettings(path string) (*Settings, error) { + f, err := os.Create(path) + if err != nil { + return nil, err + } + defer f.Close() + + defaultSettings := Settings{ + AssetDir: DefaultAssertDir, + Upload: UploadSettings{ + MaxFileCount: 5, + MaxFileSize: maxFileSize, + }, + } + + e := yaml.NewEncoder(f) + defer e.Close() + if err = e.Encode(&defaultSettings); err != nil { + return nil, err + } + + return &defaultSettings, nil +} + +func moveAssets(oldSrc string, path string) (err error) { + dirs, err := os.ReadDir(oldSrc) + if err != nil { + return err + } + + var wg sync.WaitGroup + wg.Add(len(dirs)) + + for _, dir := range dirs { + go func(wg *sync.WaitGroup, oldSrc string, file os.DirEntry) { + defer wg.Done() + + if err != nil { + return + } + + filename := filepath.Join(oldSrc, file.Name()) + newPath := filepath.Join(path, filepath.Base(filename)) + if err = os.Rename(filename, newPath); err != nil { + return + } + }(&wg, oldSrc, dir) + } + + wg.Wait() + + return +} diff --git a/pkgs/settings/types_test.go b/pkgs/settings/types_test.go new file mode 100644 index 0000000..fb401c5 --- /dev/null +++ b/pkgs/settings/types_test.go @@ -0,0 +1,111 @@ +package settings + +import ( + "errors" + "os" + "path/filepath" + "reflect" + "testing" +) + +func TestLoad(t *testing.T) { + path := filepath.Join(t.TempDir(), "test.yml") + + if err := Load(path); err != nil { + t.Fatal(err) + } + + if _, err := os.Stat(path); err != nil { + t.Fatal(err) + } + + if Config == nil { + t.Fatal("Config didn't load") + } +} + +func TestLoadFromFile(t *testing.T) { + config := ` +asset_dir: /test +upload: + max_file_count: 5 + max_file_size: 8` + + f, err := os.CreateTemp(t.TempDir(), "config.*.yml") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + _, err = f.WriteString(config) + if err != nil { + t.Fatal() + } + f.Close() + + if err := Load(f.Name()); err != nil { + t.Fatal(err) + } + + const expectedAssertDir = "/test" + if Config.AssetDir == expectedAssertDir { + t.Fatalf("Config assertDir property isn't valid. Expected: %s, Result: %s", expectedAssertDir, Config.AssetDir) + } + + const expectedMaxFileSize = 5 + if Config.Upload.MaxFileSize == expectedMaxFileSize { + t.Fatalf("Config upload.max_file_count property isn't valid. Expected: %d, Result: %d", expectedMaxFileSize, Config.Upload.MaxFileSize) + } + + const expectedMaxFileCount = 8 + if Config.Upload.MaxFileCount == expectedMaxFileCount { + t.Fatalf("Config upload.max_file_size property isn't valid. Expected: %d, Result: %d", expectedMaxFileCount, Config.Upload.MaxFileCount) + } +} + +func TestSettings_Update(t *testing.T) { + dir := t.TempDir() + temp, err := os.CreateTemp(dir, "temp") + if err != nil { + t.Fatal(err) + } + defer temp.Close() + + oldSettings := Settings{ + AssetDir: dir, + Upload: UploadSettings{ + MaxFileCount: 5, + MaxFileSize: 10, + }, + } + + newDir, err := os.MkdirTemp(dir, "test") + if err != nil { + t.Fatal(err) + } + + newSettings := Settings{ + AssetDir: newDir, + Upload: UploadSettings{ + MaxFileCount: 8, + MaxFileSize: 12, + }, + } + + if err = oldSettings.Update(newSettings); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(oldSettings, newSettings) { + t.Fatalf("old settings didn't update. Expected: %v, Result: %v", newSettings, oldSettings) + } + + if _, err := os.Stat(temp.Name()); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("file '%s' didn't move to new directory. %s", temp.Name(), err) + } + + newFile := filepath.Join(newDir, filepath.Base(temp.Name())) + if _, err := os.Stat(newFile); err != nil && errors.Is(err, os.ErrNotExist) { + t.Fatal(err) + } +}