Added unit and integration tests

This commit is contained in:
OLUWADAMILOLA OKUSANYA 2023-12-25 03:56:48 -05:00
parent f3dede1e33
commit fe8c87de18
42 changed files with 2996 additions and 97 deletions

33
.gitignore vendored
View File

@ -1,3 +1,36 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin
bin/*
rpm
rpm/*
dist
tmp/
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
coverage
.coverage
# ignore the following files/directories
**/userdata/* **/userdata/*
examples examples
tests tests
e2e
site/
.vscode/
.idea/
.DS_Store
__rd*
tests/out

View File

@ -1,11 +1,52 @@
# Inspired from https://dustinspecker.com/posts/go-combined-unit-integration-code-coverage/ and https://netdevops.me/2023/test-coverage-for-go-integration-tests/
BIN_DIR = $(CURDIR)/bin
COVERAGE_DIR = $(CURDIR)/coverage
BINARY = $(BIN_DIR)/gosimplenpm
.PHONY: clean
clean: clean:
go clean go clean
.PHONY: dep
dep: dep:
go mod tidy go mod tidy
.PHONY: fmt
fmt: fmt:
go fmt ./... go fmt ./...
.PHONY: lint
lint: lint:
golangci-lint run golangci-lint run
coverage-unit:
go test ./... -short -covermode=count -coverprofile=./coverage/unit.out
go tool cover -func=./coverage/unit.out
coverage-integration:
go test ./... -run Integration -covermode=count -coverprofile=./coverage/integration.out
go tool cover -func=./coverage/integration.out
.PHONY: build-debug
build-debug:
mkdir -p $(BIN_DIR)
go build -o $(BINARY) -cover main.go
.PHONY: test
test: build-debug
rm -rf $(COVERAGE_DIR)
mkdir -p $(COVERAGE_DIR)
go test -cover ./... -args -test.gocoverdir="$(COVERAGE_DIR)"
.PHONY: coverage-full
coverage-full: test
go tool covdata textfmt -i=$(COVERAGE_DIR) -o $(COVERAGE_DIR)/coverage.out
go tool cover -func=$(COVERAGE_DIR)/coverage.out
.PHONY: coverage-html
coverage-html:
go tool cover -html=./coverage/coverage.out -o ./coverage/coverage.html
open ./coverage/coverage.html
.PHONY: lint-all
lint-all: lint-all:
golangci-lint run --enable-all golangci-lint run --enable-all
.PHONY: dep lint clean

View File

@ -10,6 +10,22 @@ TODO...
TODO... TODO...
# Work List # TODO List
TODO... - [x] Adding unit tests
- [x] Adding integration tests
- [ ] Adding e2e tests
- [ ] Adding support for log files, that is, writing logs to a log file
- [ ] Support [abbreviated_package_format](https://github.com/verdaccio/verdaccio/issues/2792)
- [ ] Copy artifactory setup where there is a package.json (current package.json) and there is also an index json for the npm cli. Basically, the structure is like this
- [ ] Add a max bytes for publishing as shown [here](https://stackoverflow.com/questions/28282370/is-it-advisable-to-further-limit-the-size-of-forms-when-using-golang)
```sh
registry
|-.npm
|-|-@scope1/package1
|-|-|-index.json
|-@scope1/package1
|-|-package1-x.x.x.tgz
|-|-package.json
```

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"gosimplenpm/internal/config" "gosimplenpm/internal/config"
"gosimplenpm/internal/handler" "gosimplenpm/internal/handler"
"gosimplenpm/internal/storage"
"os" "os"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -47,13 +48,13 @@ Documentation about the npm private registry:
}, },
} }
app := &handler.Application{ app := &handler.Application{
Conf: cfg, Conf: cfg,
Logger: log, Logger: log,
FSStorage: &storage.FSStorage{},
} }
fmt.Println("\n Server is starting....") fmt.Println("\n Server is starting....")
err = app.Start() err = app.Start()
fmt.Println("Why!")
if err != nil { if err != nil {
fmt.Printf("Server start up error: %+v\n", err) fmt.Printf("Server start up error: %+v\n", err)
os.Exit(1) os.Exit(1)

4
go.mod
View File

@ -6,12 +6,16 @@ require (
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.10.0 golang.org/x/crypto v0.10.0
golang.org/x/mod v0.11.0 golang.org/x/mod v0.11.0
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.9.0 // indirect golang.org/x/sys v0.9.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

1
go.sum
View File

@ -25,6 +25,7 @@ golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -70,6 +70,8 @@ func VerifyConfig() error {
return err return err
} }
// TODO: Create a logging file (it should be located at .gosimplenpm/applogs)
ConfigFilePath = path.Join(configDirPath, "config.json") ConfigFilePath = path.Join(configDirPath, "config.json")
if NpmRepoDir == "" { if NpmRepoDir == "" {

View File

@ -3,6 +3,7 @@ package handler
import ( import (
"gosimplenpm/internal/config" "gosimplenpm/internal/config"
"gosimplenpm/internal/middlewares" "gosimplenpm/internal/middlewares"
"gosimplenpm/internal/storage"
"net/http" "net/http"
"time" "time"
@ -11,9 +12,10 @@ import (
) )
type Application struct { type Application struct {
Logger *logrus.Logger Logger *logrus.Logger
Conf config.Config Conf config.Config
Mux *mux.Router Mux *mux.Router
FSStorage storage.Storage
} }
func (app *Application) Routes() { func (app *Application) Routes() {
@ -24,14 +26,14 @@ func (app *Application) Routes() {
m.Use(middlewares.LogMiddleware(app.Logger)) m.Use(middlewares.LogMiddleware(app.Logger))
// main handler // main handler
m.HandleFunc("/{name}", GetPackage(app.Logger, app.Conf)).Methods("GET") m.HandleFunc("/{name}", GetPackage(app.Logger, app.Conf, app.FSStorage)).Methods("GET")
m.HandleFunc("/{name}", middlewares.AuthMiddleware(app.Conf)(Publish(app.Logger, app.Conf))).Methods("PUT") m.HandleFunc("/{name}", middlewares.AuthMiddleware(app.Conf)(Publish(app.Logger, app.Conf, app.FSStorage))).Methods("PUT")
// tar handlers // tar handlers
m.HandleFunc("/{name}/-/{tar}", PackageTarGet(app.Logger, app.Conf)).Methods("GET") m.HandleFunc("/{name}/-/{tar}", PackageTarGet(app.Logger, app.Conf, app.FSStorage)).Methods("GET")
// tag handlers // tag handlers
m.HandleFunc("/-/package/{name}/dist-tags/{tag}", middlewares.AuthMiddleware(app.Conf)(DistTagDelete(app.Logger, app.Conf))).Methods("DELETE") m.HandleFunc("/-/package/{name}/dist-tags/{tag}", middlewares.AuthMiddleware(app.Conf)(DistTagDelete(app.Logger, app.Conf, app.FSStorage))).Methods("DELETE")
m.HandleFunc("/-/package/{name}/dist-tags/{tag}", middlewares.AuthMiddleware(app.Conf)(DistTagPut(app.Logger, app.Conf))).Methods("PUT") m.HandleFunc("/-/package/{name}/dist-tags/{tag}", middlewares.AuthMiddleware(app.Conf)(DistTagPut(app.Logger, app.Conf, app.FSStorage))).Methods("PUT")
m.HandleFunc("/-/package/{name}/dist-tags", DistTagGet(app.Logger, app.Conf)).Methods("GET") m.HandleFunc("/-/package/{name}/dist-tags", DistTagGet(app.Logger, app.Conf, app.FSStorage)).Methods("GET")
m.NotFoundHandler = http.HandlerFunc(NotFound) m.NotFoundHandler = http.HandlerFunc(NotFound)
app.Mux = m app.Mux = m
} }

View File

@ -12,7 +12,7 @@ import (
"gosimplenpm/internal/storage" "gosimplenpm/internal/storage"
) )
func GetPackage(lg *logrus.Logger, cfg config.Config) http.HandlerFunc { func GetPackage(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
escapedName := mux.Vars(r)["name"] escapedName := mux.Vars(r)["name"]
packageName, _ := url.PathUnescape(escapedName) packageName, _ := url.PathUnescape(escapedName)
@ -20,7 +20,7 @@ func GetPackage(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
"function": "get-package", "function": "get-package",
}).Debugf("Package name => %s\n", packageName) }).Debugf("Package name => %s\n", packageName)
fileToServe, found, err := storage.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg) fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -1 +1,218 @@
package handler package handler
import (
"bytes"
"encoding/json"
"fmt"
"gosimplenpm/internal/config"
"gosimplenpm/internal/storage"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestUnitGet(t *testing.T) {
t.Run("return `Not Found` error if package is not found", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/{name}", nil)
wrt := httptest.NewRecorder()
log := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) {
return "", false, nil
}
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
}
req = mux.SetURLVars(req, vars)
GetPackage(log, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusNotFound)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Package not found: test-package\n")
})
t.Run("return `Internal Server` error if package cannot be retrieved", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/test-package", nil)
wrt := httptest.NewRecorder()
log := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) {
return "", true, fmt.Errorf("filesystem error")
}
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
}
req = mux.SetURLVars(req, vars)
GetPackage(log, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "filesystem error\n")
})
t.Run("return a file if package is found", func(t *testing.T) {
tmpDir := t.TempDir()
f, err := os.CreateTemp(tmpDir, "foo.json")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
defer f.Close()
_, err = f.WriteString("{data: \"test data\"}")
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodGet, "/test-oackage", nil)
wrt := httptest.NewRecorder()
log := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) {
return f.Name(), true, nil
}
// mfs.SetRetrieved(true)
// mfs.SetFileToServe(f.Name())
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
}
req = mux.SetURLVars(req, vars)
GetPackage(log, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusOK)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "{data: \"test data\"}")
})
}
func TestIntegrationGet(t *testing.T) {
if testing.Short() {
t.Skip("Skipping getPackage integration test")
}
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
tmpDir := t.TempDir()
t.Logf("Temp Dir: %s", tmpDir)
// cpFolders(t, "intestdata/@df", fmt.Sprintf("%s/@df", tmpDir))
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
indexJsonFp := "intestdata/get/index.json"
tgzFile := "intestdata/get/simplepackone-1.0.0.tgz"
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
cpFile(t, tgzFile, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
listDir(t, fmt.Sprintf("%s/", tmpDir), true)
cfg := config.Config{
RepoDir: tmpDir,
Token: token,
}
app := newTestApp(t, cfg)
app.Routes()
ts := newTestServer(t, app.Mux)
defer ts.Close()
code, _, body := ts.get(t, fmt.Sprintf("/%s", url.PathEscape("@df/simplepackone")))
assert.Equal(t, code, http.StatusOK)
expected := readTestFile(t, indexJsonFp)
var resultExpected map[string]interface{}
var resultBody map[string]interface{}
json.Unmarshal(expected, &resultExpected)
json.Unmarshal(body, &resultBody)
assert.Equal(t, resultBody, resultExpected)
}

View File

@ -0,0 +1 @@
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.0.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T11:04:34-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.0.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospsXLJ+m2HQsTJYMSW4WoCggS7TFVhYFksoLhv73HSknTtp93Dys88EAwXt77o6+I9Xmxdd8TU9ZU9JH94s8+gfI87yLszP4K76h4Qc4+nB+4Q0GFxce6nkD73Lo4Xq0B+qkygWG8jckiQQv63+ECt5IXlO35utje0oFtU+ODvT/oXbb/9sVJwBv9tz/Z+dn3/f/4Px8cOj/fdAfFoDd5BtqX4H9a7k6lWzT1lT/HXhDbUeL76mQjDdaY+B6rtdzSyoLwVq1lfTMTc7M7vk66bm9okSBhkOGolJpNVpUHD7ZRAgurqDhoAUgW1qwFaPlJxvevwf6yBQMbLT8Zrzlnaq42GHWrKCNNAnMguw5uJZiCE3B6CtYKmvWGODPl+6j0dQx86LKDXPg7bhdx0r7yv78C7I0tPXN+pn7PyH+eEbcTbn/+384HPxw/59/GB76fx/0Dm543/JNu7GsrGIS8JcDNv0TCN6tKzDiDW1Urpsd+ArFrWD3uaKAViDomkklnlzLegcTmqtOUIm+4nHsuoZJmipvCuPjreCWi68QovUL93Al/yv9HwYjEqXEVY9qz/0/9M68H+5/7/Ly0P/7IGvE2yfB1pWC4+IEhvpj7M1EmFOxYVLf/3ouVPiFsHyCtcgbRUsHVoJSPRDwBhVr6oDikDdP0OKLQU+KpcL3AGvWVg4F4mhNpSeM5Cv1kAuKyiXkUvKC4TApoeRFtxs0K1ZTCceqopadbi3sEwNS0rwG1qA3Cs8ieGD4MOgUziOcRqzQPhxUKuqu1DE8i2u2YVsEbW6Sl9ppJzEDHacDG16ylV6pSavtljWTlWOVetCxZaeQKTXTPD0cnccpFyBpXWsP+Oroc91FZ3QQxWp1QdW2RAb3oeKbt5lgiVadaBCSliZdjiUziF9oobQXrb7idc0fMDWEbEqmM5JXeoZjXZf8nppc+rNtuMJQ+xD0AbS7U92KZJVj7Etq9QVDXCxv/iodoeGxWRrFsPYtFwbv+zRxqmdTAmk8yW79hECQwjyJPwZjMgbbT3FvO3AbZNN4kQFqJH6U3UE8AT+6g9+CaOxY5Pd5QtIU4gSC2TwMyNiBIBqFi3EQ3cA12kVxBmGAjz10msWgAbeuAoJ2E2tGktEUt/51EAbZnQOTIIu0zwk69WHuJ1kwWoR+AvNFMo9TgvBjK4qjIJokiEJmJMpcREUoIB9xA+nUD0MD5S8w+sTEN4rnd0lwM81gGodjgsxrYoWBfx2SHgqTGoV+MHNg7M/8G2KsYvSSgFbbRnc7JYaFeD7+RlkQRxbWZBRHWYJbB7NMshfT2yAlDvhJkOqCTJIY3etyokVsnKBdRHovutTw5kRQRe8XKdnFMiZ+iL5Sbfxa2bUON+SBDnSgA/2c9CfXO4iiABgAAA==","length":1075}}}

View File

@ -0,0 +1,16 @@
{
"name": "@df/simplepackone",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid":"^9.x"
}
}

View File

@ -0,0 +1 @@
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.0.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T11:04:34-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.0.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospsXLJ+m2HQsTJYMSW4WoCggS7TFVhYFksoLhv73HSknTtp93Dys88EAwXt77o6+I9Xmxdd8TU9ZU9JH94s8+gfI87yLszP4K76h4Qc4+nB+4Q0GFxce6nkD73Lo4Xq0B+qkygWG8jckiQQv63+ECt5IXlO35utje0oFtU+ODvT/oXbb/9sVJwBv9tz/Z+dn3/f/4Px8cOj/fdAfFoDd5BtqX4H9a7k6lWzT1lT/HXhDbUeL76mQjDdaY+B6rtdzSyoLwVq1lfTMTc7M7vk66bm9okSBhkOGolJpNVpUHD7ZRAgurqDhoAUgW1qwFaPlJxvevwf6yBQMbLT8Zrzlnaq42GHWrKCNNAnMguw5uJZiCE3B6CtYKmvWGODPl+6j0dQx86LKDXPg7bhdx0r7yv78C7I0tPXN+pn7PyH+eEbcTbn/+384HPxw/59/GB76fx/0Dm543/JNu7GsrGIS8JcDNv0TCN6tKzDiDW1Urpsd+ArFrWD3uaKAViDomkklnlzLegcTmqtOUIm+4nHsuoZJmipvCuPjreCWi68QovUL93Al/yv9HwYjEqXEVY9qz/0/9M68H+5/7/Ly0P/7IGvE2yfB1pWC4+IEhvpj7M1EmFOxYVLf/3ouVPiFsHyCtcgbRUsHVoJSPRDwBhVr6oDikDdP0OKLQU+KpcL3AGvWVg4F4mhNpSeM5Cv1kAuKyiXkUvKC4TApoeRFtxs0K1ZTCceqopadbi3sEwNS0rwG1qA3Cs8ieGD4MOgUziOcRqzQPhxUKuqu1DE8i2u2YVsEbW6Sl9ppJzEDHacDG16ylV6pSavtljWTlWOVetCxZaeQKTXTPD0cnccpFyBpXWsP+Oroc91FZ3QQxWp1QdW2RAb3oeKbt5lgiVadaBCSliZdjiUziF9oobQXrb7idc0fMDWEbEqmM5JXeoZjXZf8nppc+rNtuMJQ+xD0AbS7U92KZJVj7Etq9QVDXCxv/iodoeGxWRrFsPYtFwbv+zRxqmdTAmk8yW79hECQwjyJPwZjMgbbT3FvO3AbZNN4kQFqJH6U3UE8AT+6g9+CaOxY5Pd5QtIU4gSC2TwMyNiBIBqFi3EQ3cA12kVxBmGAjz10msWgAbeuAoJ2E2tGktEUt/51EAbZnQOTIIu0zwk69WHuJ1kwWoR+AvNFMo9TgvBjK4qjIJokiEJmJMpcREUoIB9xA+nUD0MD5S8w+sTEN4rnd0lwM81gGodjgsxrYoWBfx2SHgqTGoV+MHNg7M/8G2KsYvSSgFbbRnc7JYaFeD7+RlkQRxbWZBRHWYJbB7NMshfT2yAlDvhJkOqCTJIY3etyokVsnKBdRHovutTw5kRQRe8XKdnFMiZ+iL5Sbfxa2bUON+SBDnSgA/2c9CfXO4iiABgAAA==","length":1075}}}

View File

@ -0,0 +1,16 @@
{
"name": "@df/simplepackone",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid":"^9.x"
}
}

View File

@ -0,0 +1 @@
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.9.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","1.9.0":"2023-12-24T13:16:16-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T13:16:16-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}},"1.9.0":{"name":"@df/simplepackone","version":"1.9.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.9.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-TU3UpYnWUeZ4wQmwpMIZGLSPWYkbAP3pOdHmlX2nelmd2zhsCRWHoOLaCyxOrsn6KAmR0M9da5ggHZ9NUnGiCA==","shasum":"047e8445501a52ce658a036f8bff356f8d762762","tarball":"http://localhost:6000/@df%2Fsimplepackone/-/@df%2Fsimplepackone-1.9.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"csv":"^6.x","eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.9.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospM4aD5NselYqCwZktwsQFFAlmiLrSwKJJUXDP3vO9JOnLT7uHlY54MBgvf23B19R6rNi2/5ip6ypqSP7ld59A+Q53mD83P4K76h/hkcnV0MvF5vMPBQz+t5l30P16M9UCdVLjCUvyFJJHhZ/yNU8Ebymro1Xx3bEyqofXJ0oP8Ptdv+3644AXiz5/4/vxz82P+9i4uLQ//vg/6wAOwmX1P7CuzfyuWpZOu2pvrvwBtqO1p8T4VkvNEaPfeD6224JZWFYK3aSjbMdc7M7vk62XA3ihIFGg4Zikql1WhRcfhsEyG4uIKGgxaAbGnBloyWn214/x7oI1PQs9Hyu/GWd6riYodZs4I20iQwDbLn4FqKITQFo69gqaxZY4C/XLqPRlPHzIsqN8yet+N2HSvtK/vLhx2rkPeaM0CODsb6bv06/Z8QfzQl7rrc//3f7/d+uv8vzvqH/t8HvYMbvmn5pl1bVlYxCfjLAZv+CQTvVhUY8Zo2KtfNDnyJ4law+1xRQCsQdMWkEk+uZb2DMc1VJ6hEX/Eodl3DJE2VN4Xx8VZwy8U3CNH6hXu4kv+V/g+DIYlS4qpHtef+73vn3k/3v3d5eej/fZA15O2TYKtKwXFxAn39MfZmIsyoWDOp7389Fyr8Qlg8wUrkjaKlA0tBqR4IeIOKFXVAccibJ2jxxaAnxULhe4A1KyuHAnG0ptITRvKlesgFReUScil5wXCYlFDyotsNmiWrqYRjVVHLTrcW9okBKWleA2vQG4VnETwwfBh0CucRTiNWaB8OKhV1V+oYnsU1W7MtgjY3yUvttJOYgY7TgTUv2VKv1KTVdouaycqxSj3o2KJTyJSaaZ4ejs7jlAuQtK61B3x1bHLdRWd0EMVqdUHVtkQG96Hi67eZYImWnWgQkpYmXY4lM4hfaaG0F62+5HXNHzA1hGxKpjOSV3qGY10X/J6aXDZn23CFoW5C0AfQ7k51K5JVjrEvqLUpGOJiefNX6QgNj83SKIa1b7kweD+miVM9mxBI43F26ycEghRmSfwpGJER2H6Ke9uB2yCbxPMMUCPxo+wO4jH40R18DKKRY5HfZwlJU4gTCKazMCAjB4JoGM5HQXQD12gXxRmEAT720GkWgwbcugoI2o2tKUmGE9z610EYZHcOjIMs0j7H6NSHmZ9kwXAe+gnM5sksTgnCj6wojoJonCAKmZIocxEVoYB8wg2kEz8MDZQ/x+gTE98wnt0lwc0kg0kcjggyr4kVBv51SDZQmNQw9IOpAyN/6t8QYxWjlwS02ja62wkxLMTz8TfMgjiysCbDOMoS3DqYZZK9mN4GKXHAT4JUF2ScxOhelxMtYuME7SKy8aJLDW9OBFX0fp6SXSwj4ofoK9XGr5Vd63BDHuhABzrQr0l/AlppbUsAGAAA","length":1083}}}

View File

@ -0,0 +1,17 @@
{
"name": "@df/simplepackone",
"version": "1.9.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid":"^9.x",
"csv":"^6.x"
}
}

View File

@ -0,0 +1 @@
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.9.0", "pre-alpha":"1.0.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","1.9.0":"2023-12-24T13:16:16-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T13:16:16-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}},"1.9.0":{"name":"@df/simplepackone","version":"1.9.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.9.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-TU3UpYnWUeZ4wQmwpMIZGLSPWYkbAP3pOdHmlX2nelmd2zhsCRWHoOLaCyxOrsn6KAmR0M9da5ggHZ9NUnGiCA==","shasum":"047e8445501a52ce658a036f8bff356f8d762762","tarball":"http://localhost:6000/@df%2Fsimplepackone/-/@df%2Fsimplepackone-1.9.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"csv":"^6.x","eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.9.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospM4aD5NselYqCwZktwsQFFAlmiLrSwKJJUXDP3vO9JOnLT7uHlY54MBgvf23B19R6rNi2/5ip6ypqSP7ld59A+Q53mD83P4K76h/hkcnV0MvF5vMPBQz+t5l30P16M9UCdVLjCUvyFJJHhZ/yNU8Ebymro1Xx3bEyqofXJ0oP8Ptdv+3644AXiz5/4/vxz82P+9i4uLQ//vg/6wAOwmX1P7CuzfyuWpZOu2pvrvwBtqO1p8T4VkvNEaPfeD6224JZWFYK3aSjbMdc7M7vk62XA3ihIFGg4Zikql1WhRcfhsEyG4uIKGgxaAbGnBloyWn214/x7oI1PQs9Hyu/GWd6riYodZs4I20iQwDbLn4FqKITQFo69gqaxZY4C/XLqPRlPHzIsqN8yet+N2HSvtK/vLhx2rkPeaM0CODsb6bv06/Z8QfzQl7rrc//3f7/d+uv8vzvqH/t8HvYMbvmn5pl1bVlYxCfjLAZv+CQTvVhUY8Zo2KtfNDnyJ4law+1xRQCsQdMWkEk+uZb2DMc1VJ6hEX/Eodl3DJE2VN4Xx8VZwy8U3CNH6hXu4kv+V/g+DIYlS4qpHtef+73vn3k/3v3d5eej/fZA15O2TYKtKwXFxAn39MfZmIsyoWDOp7389Fyr8Qlg8wUrkjaKlA0tBqR4IeIOKFXVAccibJ2jxxaAnxULhe4A1KyuHAnG0ptITRvKlesgFReUScil5wXCYlFDyotsNmiWrqYRjVVHLTrcW9okBKWleA2vQG4VnETwwfBh0CucRTiNWaB8OKhV1V+oYnsU1W7MtgjY3yUvttJOYgY7TgTUv2VKv1KTVdouaycqxSj3o2KJTyJSaaZ4ejs7jlAuQtK61B3x1bHLdRWd0EMVqdUHVtkQG96Hi67eZYImWnWgQkpYmXY4lM4hfaaG0F62+5HXNHzA1hGxKpjOSV3qGY10X/J6aXDZn23CFoW5C0AfQ7k51K5JVjrEvqLUpGOJiefNX6QgNj83SKIa1b7kweD+miVM9mxBI43F26ycEghRmSfwpGJER2H6Ke9uB2yCbxPMMUCPxo+wO4jH40R18DKKRY5HfZwlJU4gTCKazMCAjB4JoGM5HQXQD12gXxRmEAT720GkWgwbcugoI2o2tKUmGE9z610EYZHcOjIMs0j7H6NSHmZ9kwXAe+gnM5sksTgnCj6wojoJonCAKmZIocxEVoYB8wg2kEz8MDZQ/x+gTE98wnt0lwc0kg0kcjggyr4kVBv51SDZQmNQw9IOpAyN/6t8QYxWjlwS02ja62wkxLMTz8TfMgjiysCbDOMoS3DqYZZK9mN4GKXHAT4JUF2ScxOhelxMtYuME7SKy8aJLDW9OBFX0fp6SXSwj4ofoK9XGr5Vd63BDHuhABzrQr0l/AlppbUsAGAAA","length":1083}}}

View File

@ -0,0 +1,17 @@
{
"name": "@df/simplepackone",
"version": "1.9.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid":"^9.x",
"csv":"^6.x"
}
}

View File

@ -0,0 +1 @@
{"_id":"@df/simplepackone","name":"@df/simplepackone","description":"This is a very rough implementation of a private npm registry.","dist-tags":{"latest":"1.0.0"},"time":{"1.0.0":"2023-12-24T11:04:34-05:00","created":"2023-12-24T11:04:34-05:00","modified":"2023-12-24T11:04:34-05:00","unpublished":""},"versions":{"1.0.0":{"name":"@df/simplepackone","version":"1.0.0","description":"This is a very rough implementation of a private npm registry.","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" \u0026\u0026 exit 1"},"license":"MIT","repository":{"type":"","url":"","directory":""},"readme":"# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...","readmeFilename":"README.md","_id":"@df/simplepackone@1.0.0","maintainers.omitempty":null,"bugs":{},"bin":null,"engines":{},"_nodeVersion":"16.20.0","_npmVersion":"8.19.4","dist":{"integrity":"sha512-9NI+Kqf+4C8Rr5GPKen8o6hhp/LMMeox96du65v6T+W27irVSsxZP0grBHBFyfd9whDXOoSliRWvYBnGnELlnA==","shasum":"a3974e824557b8e7ba00b87fb4ec51e4d132fff2","tarball":"http://localhost:6000/@df/simplepackone/-/@df/simplepackone-1.0.0.tgz","fileCount":0,"unpackedSize":0},"dependencies":{"eslint":"^7.x","mocha":"^10.x","uuid":"^9.x"}}},"access":"","_attachments":{"@df/simplepackone-1.0.0.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAC/+1XbW/bNhDOZ/2KgwoUCSAospsXLJ+m2HQsTJYMSW4WoCggS7TFVhYFksoLhv73HSknTtp93Dys88EAwXt77o6+I9Xmxdd8TU9ZU9JH94s8+gfI87yLszP4K76h4Qc4+nB+4Q0GFxce6nkD73Lo4Xq0B+qkygWG8jckiQQv63+ECt5IXlO35utje0oFtU+ODvT/oXbb/9sVJwBv9tz/Z+dn3/f/4Px8cOj/fdAfFoDd5BtqX4H9a7k6lWzT1lT/HXhDbUeL76mQjDdaY+B6rtdzSyoLwVq1lfTMTc7M7vk66bm9okSBhkOGolJpNVpUHD7ZRAgurqDhoAUgW1qwFaPlJxvevwf6yBQMbLT8Zrzlnaq42GHWrKCNNAnMguw5uJZiCE3B6CtYKmvWGODPl+6j0dQx86LKDXPg7bhdx0r7yv78C7I0tPXN+pn7PyH+eEbcTbn/+384HPxw/59/GB76fx/0Dm543/JNu7GsrGIS8JcDNv0TCN6tKzDiDW1Urpsd+ArFrWD3uaKAViDomkklnlzLegcTmqtOUIm+4nHsuoZJmipvCuPjreCWi68QovUL93Al/yv9HwYjEqXEVY9qz/0/9M68H+5/7/Ly0P/7IGvE2yfB1pWC4+IEhvpj7M1EmFOxYVLf/3ouVPiFsHyCtcgbRUsHVoJSPRDwBhVr6oDikDdP0OKLQU+KpcL3AGvWVg4F4mhNpSeM5Cv1kAuKyiXkUvKC4TApoeRFtxs0K1ZTCceqopadbi3sEwNS0rwG1qA3Cs8ieGD4MOgUziOcRqzQPhxUKuqu1DE8i2u2YVsEbW6Sl9ppJzEDHacDG16ylV6pSavtljWTlWOVetCxZaeQKTXTPD0cnccpFyBpXWsP+Oroc91FZ3QQxWp1QdW2RAb3oeKbt5lgiVadaBCSliZdjiUziF9oobQXrb7idc0fMDWEbEqmM5JXeoZjXZf8nppc+rNtuMJQ+xD0AbS7U92KZJVj7Etq9QVDXCxv/iodoeGxWRrFsPYtFwbv+zRxqmdTAmk8yW79hECQwjyJPwZjMgbbT3FvO3AbZNN4kQFqJH6U3UE8AT+6g9+CaOxY5Pd5QtIU4gSC2TwMyNiBIBqFi3EQ3cA12kVxBmGAjz10msWgAbeuAoJ2E2tGktEUt/51EAbZnQOTIIu0zwk69WHuJ1kwWoR+AvNFMo9TgvBjK4qjIJokiEJmJMpcREUoIB9xA+nUD0MD5S8w+sTEN4rnd0lwM81gGodjgsxrYoWBfx2SHgqTGoV+MHNg7M/8G2KsYvSSgFbbRnc7JYaFeD7+RlkQRxbWZBRHWYJbB7NMshfT2yAlDvhJkOqCTJIY3etyokVsnKBdRHovutTw5kRQRe8XKdnFMiZ+iL5Sbfxa2bUON+SBDnSgA/2c9CfXO4iiABgAAA==","length":1075}}}

View File

@ -0,0 +1,16 @@
{
"name": "@df/simplepackone",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid":"^9.x"
}
}

View File

@ -9,7 +9,9 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strconv"
"strings" "strings"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -19,7 +21,7 @@ type NPMClientPutRequest struct {
Request serviceidos.IndexJson Request serviceidos.IndexJson
} }
func Publish(lg *logrus.Logger, cfg config.Config) http.HandlerFunc { func Publish(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// (1) Parse Json Body // (1) Parse Json Body
// (2) Check if package exists in the folder. // (2) Check if package exists in the folder.
@ -45,6 +47,7 @@ func Publish(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
} }
// Extract relevant data from index.json // Extract relevant data from index.json
fmt.Printf("cRequest => %+v\n", cr)
index := 0 index := 0
var tag string var tag string
var version string var version string
@ -59,26 +62,34 @@ func Publish(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
index++ index++
} }
versionData = cr.Request.Versions[version] versionData = cr.Request.Versions[version]
lg.WithFields(logrus.Fields{ lg.WithFields(logrus.Fields{
"function": "publish", "function": "publish",
}).Debugf("For version(%s) with tag(%s), versionData => %+v\n", version, tag, versionData) }).Debugf("For version(%s) with tag(%s), versionData => %+v\n", version, tag, versionData)
// Rewrite the tarball path // Rewrite the tarball path
tarballFileName := strings.Split(versionData.Dist.Tarball, "/-/")[1] tarballFileName := strings.Split(versionData.Dist.Tarball, "/-/")[1]
tarballFileName, _ = url.PathUnescape(tarballFileName)
lg.WithFields(logrus.Fields{ lg.WithFields(logrus.Fields{
"function": "publish", "function": "publish",
}).Debugf("TarballName => %s\n", tarballFileName) }).Debugf("TarballName => %s\n", tarballFileName)
// versionData.Dist.Tarball = fmt.Sprintf("file://%s", packageFilePath) // versionData.Dist.Tarball = fmt.Sprintf("file://%s", packageFilePath)
versionData.Dist.Tarball = fmt.Sprintf("http://%s/%s/-/%s", r.Host, url.PathEscape(packageName), url.PathEscape(tarballFileName)) versionData.Dist.Tarball = fmt.Sprintf("http://%s/%s/-/%s", r.Host, url.PathEscape(packageName), url.PathEscape(tarballFileName))
lg.WithFields(logrus.Fields{ lg.WithFields(logrus.Fields{
"function": "publish", "function": "publish",
}).Debugf("versionData.Dist.Tarball => %s\n", versionData.Dist.Tarball) }).Debugf("versionData.Dist.Tarball => %s\n", versionData.Dist.Tarball)
tarBallFile := strings.Split(tarballFileName, "/")[1] tarBallFile := strings.Split(tarballFileName, "/")[1]
packageFilePath := path.Join(cfg.RepoDir, packageName, tarBallFile) packageFilePath := path.Join(cfg.RepoDir, packageName, tarBallFile)
lg.WithFields(logrus.Fields{
"function": "publish",
}).Debugf("PackageFilePath => %s\n", packageFilePath)
fmt.Printf("PackageFilePath => %s\n", packageFilePath)
// Try to get the index.json from the store // Try to get the index.json from the store
fileToServe, found, err := storage.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg) fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -89,9 +100,16 @@ func Publish(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
// new package // new package
jsonFile = cr.Request jsonFile = cr.Request
jsonFile.DistTags["latest"] = version jsonFile.DistTags["latest"] = version
curTime := time.Now().Format(time.RFC3339)
jsonFile.TimesPackage = map[string]string{
version: curTime,
"created": curTime,
"modified": curTime,
"unpublished": "",
}
} else { } else {
// old package // old package
err = storage.ReadIndexJson(fileToServe, &jsonFile, lg) err = stg.ReadIndexJson(fileToServe, &jsonFile, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -102,7 +120,7 @@ func Publish(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
lg.WithFields(logrus.Fields{ lg.WithFields(logrus.Fields{
"function": "publish", "function": "publish",
}).Debugf("Version %s of package %s already exists!!\n", version, packageName) }).Debugf("Version %s of package %s already exists!!\n", version, packageName)
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, fmt.Sprintf("Version %s of package %s already exists!!\n", version, packageName), http.StatusBadRequest)
return return
} }
@ -114,6 +132,12 @@ func Publish(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
// Merge in the new version data // Merge in the new version data
jsonFile.Versions[version] = versionData jsonFile.Versions[version] = versionData
// Update the time field
timesPackages := jsonFile.TimesPackage
curTime := time.Now().Format(time.RFC3339)
timesPackages["modified"] = curTime
timesPackages[version] = curTime
} }
lg.WithFields(logrus.Fields{ lg.WithFields(logrus.Fields{
@ -121,7 +145,7 @@ func Publish(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
}).Debugln("FiletoServe ==> ", fileToServe) }).Debugln("FiletoServe ==> ", fileToServe)
// Write index.json // Write index.json
err = storage.WriteIndexJson(fileToServe, &jsonFile, lg) err = stg.WriteIndexJson(fileToServe, &jsonFile, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -132,11 +156,22 @@ func Publish(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
}).Debugln("Package path => ", packageFilePath) }).Debugln("Package path => ", packageFilePath)
// Write bundled package // Write bundled package
packageData := jsonFile.Attachments[fmt.Sprintf("%s-%s.tgz", packageName, version)].Data packageData := jsonFile.Attachments[fmt.Sprintf("%s-%s.tgz", packageName, version)].Data
err = storage.WritePackageToStore(packageFilePath, packageData, lg) err = stg.WritePackageToStore(packageFilePath, packageData, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
response := serviceidos.PublishPutResponse{
Ok: true,
Name: packageName,
}
jsonString, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
w.Header().Set("Content-Length", strconv.Itoa(len(jsonString)))
w.Write(jsonString)
} }
} }

View File

@ -0,0 +1,715 @@
package handler
import (
"bytes"
"fmt"
"gosimplenpm/internal/config"
"gosimplenpm/internal/serviceidos"
"gosimplenpm/internal/storage"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestUnitPublish(t *testing.T) {
t.Run("return `Internal Server` error if index.json cannot be retrieved", func(t *testing.T) {
jsonBody := []byte(
`{
"_id": "@ookusanya/simplepackone",
"name": "@ookusanya/simplepackone",
"description": "This is a very rough implementation of a private npm registry.",
"dist-tags": {
"latest": "1.2.0"
},
"versions": {
"1.2.0": {
"name": "@ookusanya/simplepackone",
"version": "1.2.0",
"description": "This is a very rough implementation of a private npm registry.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "MIT",
"files": null,
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
"_id": "@ookusanya/simplepackone@1.2.0",
"_nodeVersion": "16.20.0",
"_npmVersion": "8.19.4",
"dist": {
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
},
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid": "^9.x"
}
}
},
"access": "",
"_attachments": {
"@ookusanya/simplepackone-1.2.0.tgz": {
"content_type": "application/octet-stream",
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
"length": 1089
}
}
}`,
)
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", false, fmt.Errorf("Filesystem error")
}
vars := map[string]string{
"name": "@ookusanya%2Fsimplepackone",
}
req = mux.SetURLVars(req, vars)
Publish(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Filesystem error\n")
})
t.Run("return `Internal Server` error if index.json cannot be decoded", func(t *testing.T) {
jsonBody := []byte(
`{
"_id": "@ookusanya/simplepackone",
"name": "@ookusanya/simplepackone",
"description": "This is a very rough implementation of a private npm registry.",
"dist-tags": {
"latest": "1.2.0"
},
"versions": {
"1.2.0": {
"name": "@ookusanya/simplepackone",
"version": "1.2.0",
"description": "This is a very rough implementation of a private npm registry.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "MIT",
"files": null,
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
"_id": "@ookusanya/simplepackone@1.2.0",
"_nodeVersion": "16.20.0",
"_npmVersion": "8.19.4",
"dist": {
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
},
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid": "^9.x"
}
}
},
"access": "",
"_attachments": {
"@ookusanya/simplepackone-1.2.0.tgz": {
"content_type": "application/octet-stream",
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
"length": 1089
}
}
}`,
)
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return string(jsonBody), true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return fmt.Errorf("Filesystem error")
}
vars := map[string]string{
"name": "@ookusanya%2Fsimplepackone",
}
req = mux.SetURLVars(req, vars)
Publish(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Filesystem error\n")
})
t.Run("return `Bad Request` error if version to publish already exists", func(t *testing.T) {
jsonBody := []byte(
`{
"_id": "@ookusanya/simplepackone",
"name": "@ookusanya/simplepackone",
"description": "This is a very rough implementation of a private npm registry.",
"dist-tags": {
"latest": "1.2.0"
},
"versions": {
"1.2.0": {
"name": "@ookusanya/simplepackone",
"version": "1.2.0",
"description": "This is a very rough implementation of a private npm registry.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "MIT",
"files": null,
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
"_id": "@ookusanya/simplepackone@1.2.0",
"_nodeVersion": "16.20.0",
"_npmVersion": "8.19.4",
"dist": {
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
},
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid": "^9.x"
}
}
},
"access": "",
"_attachments": {
"@ookusanya/simplepackone-1.2.0.tgz": {
"content_type": "application/octet-stream",
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
"length": 1089
}
}
}`,
)
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return string(jsonBody), true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
temp := serviceidos.IndexJson{
Versions: map[string]serviceidos.IndexJsonVersions{
"1.2.0": {
Version: "1.2.0",
Name: "@ookusanya/simplepackone",
},
},
}
*res = temp
return nil
}
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return fmt.Errorf("Filesystem error")
}
vars := map[string]string{
"name": "@ookusanya%2Fsimplepackone",
}
req = mux.SetURLVars(req, vars)
Publish(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Version 1.2.0 of package @ookusanya/simplepackone already exists!!\n\n")
})
t.Run("return `Internal Server` error if writing index.json fails", func(t *testing.T) {
jsonBody := []byte(
`{
"_id": "@ookusanya/simplepackone",
"name": "@ookusanya/simplepackone",
"description": "This is a very rough implementation of a private npm registry.",
"dist-tags": {
"latest": "1.3.0"
},
"versions": {
"1.3.0": {
"name": "@ookusanya/simplepackone",
"version": "1.3.0",
"description": "This is a very rough implementation of a private npm registry.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "MIT",
"files": null,
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
"_id": "@ookusanya/simplepackone@1.2.0",
"_nodeVersion": "16.20.0",
"_npmVersion": "8.19.4",
"dist": {
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
},
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid": "^9.x"
}
}
},
"access": "",
"_attachments": {
"@ookusanya/simplepackone-1.3.0.tgz": {
"content_type": "application/octet-stream",
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
"length": 1089
}
}
}`,
)
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return string(jsonBody), true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
temp := serviceidos.IndexJson{
Versions: map[string]serviceidos.IndexJsonVersions{
"1.2.0": {
Version: "1.2.0",
Name: "@ookusanya/simplepackone",
},
},
DistTags: map[string]string{
"latest": "1.2.0",
},
TimesPackage: map[string]string{
"modified": "",
"created": "",
},
}
*res = temp
return nil
}
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return fmt.Errorf("Filesystem error")
}
vars := map[string]string{
"name": "@ookusanya%2Fsimplepackone",
}
req = mux.SetURLVars(req, vars)
Publish(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Filesystem error\n")
})
t.Run("return `Internal Server` error if writing tar package fails", func(t *testing.T) {
jsonBody := []byte(
`{
"_id": "@ookusanya/simplepackone",
"name": "@ookusanya/simplepackone",
"description": "This is a very rough implementation of a private npm registry.",
"dist-tags": {
"latest": "1.3.0"
},
"versions": {
"1.3.0": {
"name": "@ookusanya/simplepackone",
"version": "1.3.0",
"description": "This is a very rough implementation of a private npm registry.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "MIT",
"files": null,
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
"_id": "@ookusanya/simplepackone@1.2.0",
"_nodeVersion": "16.20.0",
"_npmVersion": "8.19.4",
"dist": {
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
},
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid": "^9.x"
}
}
},
"access": "",
"_attachments": {
"@ookusanya/simplepackone-1.3.0.tgz": {
"content_type": "application/octet-stream",
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
"length": 1089
}
}
}`,
)
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return string(jsonBody), true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
temp := serviceidos.IndexJson{
Versions: map[string]serviceidos.IndexJsonVersions{
"1.2.0": {
Version: "1.2.0",
Name: "@ookusanya/simplepackone",
},
},
DistTags: map[string]string{
"latest": "1.2.0",
},
TimesPackage: map[string]string{
"modified": "",
"created": "",
},
}
*res = temp
return nil
}
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return nil
}
mfs.WritePackageToStoreFunc = func(fPath string, data string, lg *logrus.Logger) error {
return fmt.Errorf("Filesystem error")
}
vars := map[string]string{
"name": "@ookusanya%2Fsimplepackone",
}
req = mux.SetURLVars(req, vars)
Publish(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Filesystem error\n")
})
t.Run("return 201 Created if publish is successful", func(t *testing.T) {
jsonBody := []byte(
`{
"_id": "@ookusanya/simplepackone",
"name": "@ookusanya/simplepackone",
"description": "This is a very rough implementation of a private npm registry.",
"dist-tags": {
"latest": "1.3.0"
},
"versions": {
"1.3.0": {
"name": "@ookusanya/simplepackone",
"version": "1.3.0",
"description": "This is a very rough implementation of a private npm registry.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "MIT",
"files": null,
"readme": "# Gosimplenpm\n\nThis is a very rough implementation of a private npm registry.\n\n# Features\n\nTODO...\n\n# Enhancements\n\nTODO...\n\n# Work List\n\nTODO...",
"_id": "@ookusanya/simplepackone@1.2.0",
"_nodeVersion": "16.20.0",
"_npmVersion": "8.19.4",
"dist": {
"integrity": "sha512-coOiU+ywV/do/+HwK91mfuei9491yfQUedLWjspAjDa58RniJvQNTF/cXDp/sooVdpjgEbCqKVyLOju6C1i3pw==",
"shasum": "1adafdbe8878372c9f4b554bf30d5bfb1b7896c5",
"tarball": "http://localhost:4000/@ookusanya%2Fsimplepackone/-/@ookusanya%2Fsimplepackone-1.2.0.tgz"
},
"dependencies": {
"eslint": "^7.x",
"mocha": "^10.x",
"uuid": "^9.x"
}
}
},
"access": "",
"_attachments": {
"@ookusanya/simplepackone-1.3.0.tgz": {
"content_type": "application/octet-stream",
"data": "H4sIAAAAAAAC/+1XbWvjOBDuZ/+KwQtLCyZ10pfl+uncRGnMOnawne0VlgXVVmJtHctIcl849r/fSEmb9nof73LcXoaA0Lw9M6PMSG5pcUeX7Jg3JXvsfVcH/wD5vn9+egp/xbc0OIGDk7Nzv98/P/dRz+/7nwY+rgc7oE5pKjGUvyFJJHhZ/yNUiEaJmvVqsTx0J0wy9+hgT/8fajf9v1lxAohmx/1/ev6u//tnZ/19/++CfncA3IaumHsB7q9C3HWKNk/0WPFVWzPzrxANcz2jdc+k4qIxiv3eoOevuSVTheSt3kjWzBXldvd8q6y5a0WFAoOKDM2UNmqsqAR8dYmUQl5AI8AIQLWs4AvOyq8ufPwI7JFr6Lto+cN6o52uhNxi1rxgjbJ5TMP8ObiWYQhNwdkrWKZq3ljgb596j1bTxCyKilpm399yu46X7oX77RdkGWjnh/NT9n9KgtGU9Fbl7u//waD/7v4/Oxns+38X9AGuxLrXm3blOHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZqKNoX18VZwLeQdRGj9wt1fyf9K/0fhkMQZ6elHveP+H/hng3f3f9/f9/9OyBmK9knyZaXhsDiCgfkYS+rugZZ0xWtRU0g2TwLHmTG54sq8AcyIqPBj4fYJlpI2mpUeLCRjZjbgLSqXzAMtAM2gxVeDGRq3Gt8EvFk6FAqENJraDBslFvqBSobKJVClRMFxrpRQiqLbzpwFr5mCQ10xx802Fu6RBSkZrYE36I3BswgeOD4OOo2jCQcTL4wPD5WKuitNDM/imq/4BsGY2zoo47RTmIGJ04OVKPnCrMym1Xa3NVeV55Rm5vHbTiNTGaZ9fngmj2MhQbG6Nh7w5bHOdRud1UEUpzUF1ZsSWdyHSqzeZoIlWnSyQUhW2nQFlswifmeFNl6M+kLUtXjA1BCyKbnJSF2YcY51vRX3zOayPuZGaAx1HYI5gHZ7qhuRqijGfsucdcEQF8tLX6UjDTz2TaM51r4V0uL9OU0c8PmEQJaM8+sgJRBmMEuTL+GIjMANMty7HlyH+SSZ54AaaRDnN5CMIYhv4HMYjzyH/DZLSZZBkkI4nUUhGXkQxsNoPgrjK7hEuzjJIQrxwYdO8wQM4MZVSNBu7ExJOpzgNrgMozC/8WAc5rHxOUanAcyCNA+H8yhIYTZPZ0lGEH7kxEkcxuMUUciUxHkPUREKyBfcQDYJoshCBXOMPrXxDZPZTRpeTXKYJNGIIPOSOFEYXEZkDYVJDaMgnHowCqbBFbFWCXpJwahtorueEMtCvAB/wzxMYgdrMkziPMWth1mm+YvpdZgRD4I0zExBxmmC7k050SKxTtAuJmsvptTw5kRQxeznGdnGMiJBhL4yY/xaeX8x72lPe9rTT0V/APlm4rcAGAAA",
"length": 1089
}
}
}`,
)
req := httptest.NewRequest(http.MethodPut, "/{name}", bytes.NewReader(jsonBody))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return string(jsonBody), false, nil
}
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return nil
}
mfs.WritePackageToStoreFunc = func(fPath string, data string, lg *logrus.Logger) error {
return nil
}
vars := map[string]string{
"name": "@ookusanya%2Fsimplepackone",
}
req = mux.SetURLVars(req, vars)
Publish(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusCreated)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "{\"ok\":true,\"package_name\":\"@ookusanya/simplepackone\"}")
})
}
func TestIntegrationPublishNormal(t *testing.T) {
if testing.Short() {
t.Skip("Skipping publishPackage integration test")
}
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
tmpDir := t.TempDir()
t.Logf("Temp Dir: %s", tmpDir)
indexJsonFp := "intestdata/publish/normal/index.json"
isEmpty := IsDirEmpty(t, tmpDir)
assert.Equal(t, true, isEmpty)
cfg := config.Config{
RepoDir: tmpDir,
Token: token,
}
app := newTestApp(t, cfg)
app.Routes()
ts := newTestServer(t, app.Mux)
defer ts.Close()
dataToSend := readTestFile(t, indexJsonFp)
code, _, body := ts.put(t, fmt.Sprintf("/%s", url.PathEscape("@df/simplepackone")), token, string(dataToSend))
assert.Equal(t, code, http.StatusCreated)
isEmpty = IsDirEmpty(t, tmpDir)
assert.Equal(t, false, isEmpty)
// t.Logf("Body ==> %s", string(body))
filePaths := listDir(t, tmpDir, false)
assert.Equal(t, true, containsSub(filePaths, "index.json"))
assert.Equal(t, true, containsSub(filePaths, "simplepackone-1.0.0"))
assert.NotEmpty(t, string(body))
}
func TestIntegrationPublishOverwrite(t *testing.T) {
if testing.Short() {
t.Skip("Skipping publishPackage integration test")
}
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
tmpDir := t.TempDir()
t.Logf("Temp Dir: %s", tmpDir)
// Copy initial package
indexJsonFp := "intestdata/publish/normal/index.json"
tgzFp := "intestdata/get/simplepackone-1.0.0.tgz"
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
isEmpty := IsDirEmpty(t, tmpDir)
assert.Equal(t, false, isEmpty)
indexJsonFp = "intestdata/publish/overwrite/index.json"
cfg := config.Config{
RepoDir: tmpDir,
Token: token,
}
app := newTestApp(t, cfg)
app.Routes()
ts := newTestServer(t, app.Mux)
defer ts.Close()
dataToSend := readTestFile(t, indexJsonFp)
code, _, body := ts.put(t, fmt.Sprintf("/%s", url.PathEscape("@df/simplepackone")), token, string(dataToSend))
assert.Equal(t, code, http.StatusCreated)
filePaths := listDir(t, tmpDir, false)
assert.Equal(t, true, containsSub(filePaths, "index.json"))
assert.Equal(t, true, containsSub(filePaths, "simplepackone-1.0.0"))
assert.Equal(t, true, containsSub(filePaths, "simplepackone-1.9.0"))
assert.NotEmpty(t, string(body))
}

View File

@ -15,7 +15,7 @@ import (
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
) )
func DistTagDelete(lg *logrus.Logger, cfg config.Config) http.HandlerFunc { func DistTagDelete(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
escapedName := mux.Vars(r)["name"] escapedName := mux.Vars(r)["name"]
packageName, _ := url.PathUnescape(escapedName) packageName, _ := url.PathUnescape(escapedName)
@ -30,7 +30,7 @@ func DistTagDelete(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
}).Debugf("Tag => %s\n", tag) }).Debugf("Tag => %s\n", tag)
if semver.IsValid(tag) { if semver.IsValid(tag) {
http.Error(w, "Tag cannot be a semver version", http.StatusBadRequest) http.Error(w, fmt.Sprintf("Tag %s cannot be a semver version", tag), http.StatusBadRequest)
return return
} }
@ -39,7 +39,7 @@ func DistTagDelete(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
return return
} }
fileToServe, found, err := storage.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg) fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -52,7 +52,7 @@ func DistTagDelete(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
} }
var jsonFile serviceidos.IndexJson var jsonFile serviceidos.IndexJson
err = storage.ReadIndexJson(fileToServe, &jsonFile, lg) err = stg.ReadIndexJson(fileToServe, &jsonFile, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -61,7 +61,7 @@ func DistTagDelete(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
delete(jsonFile.DistTags, tag) delete(jsonFile.DistTags, tag)
// Write index.json // Write index.json
err = storage.WriteIndexJson(fileToServe, &jsonFile, lg) err = stg.WriteIndexJson(fileToServe, &jsonFile, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -0,0 +1,429 @@
package handler
import (
"bytes"
"encoding/json"
"fmt"
"gosimplenpm/internal/config"
"gosimplenpm/internal/serviceidos"
"gosimplenpm/internal/storage"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestUnitTagDelete(t *testing.T) {
t.Run("return `Bad request` error if tag is semver", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
tag := "v1.0.0"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagDelete(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), fmt.Sprintf("Tag %s cannot be a semver version\n", tag))
})
t.Run("return `Bad request` error if tag is latest", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
tag := "latest"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagDelete(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Cannot delete the latest tag\n")
})
t.Run("return `Internal Server` error if index json cannot be retrieved", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", false, fmt.Errorf("Filesystem error")
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagDelete(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Filesystem error\n")
})
t.Run("return `Not found` error if package cannot be retrieved", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", false, nil
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagDelete(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusNotFound)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Package not found: test-package\n")
})
t.Run("return `Internal Server` error if package.json cannot be parsed", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return fmt.Errorf("Parsing failed")
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagDelete(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Parsing failed\n")
})
t.Run("return `Internal Server` error if package.json cannot be written", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
temp := serviceidos.IndexJson{
DistTags: map[string]string{
"current": "1.2.0",
"latest": "1.2.0",
},
}
*res = temp
return nil
}
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return fmt.Errorf("File cannot be written")
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagDelete(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "File cannot be written\n")
})
t.Run("return 200 OK if tag can be deleted", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
temp := serviceidos.IndexJson{
DistTags: map[string]string{
"current": "1.2.0",
"latest": "1.2.0",
},
}
*res = temp
return nil
}
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return nil
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagDelete(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusOK)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "{\"ok\":true,\"id\":\"current\",\"dist-tags\":\"{\\\"latest\\\":\\\"1.2.0\\\"}\"}")
})
}
func TestIntegrationTagDelete(t *testing.T) {
if testing.Short() {
t.Skip("Skipping deleteTags integration test")
}
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
tmpDir := t.TempDir()
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
mkDir(t, fmt.Sprintf("%s/output", tmpDir))
indexJsonFp := "intestdata/tags/index.json"
oldTgzFp := "intestdata/tags/simplepackone-1.0.0.tgz"
tgzFp := "intestdata/tags/simplepackone-1.9.0.tgz"
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
cpFile(t, oldTgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.9.0.tgz", tmpDir))
cfg := config.Config{
RepoDir: tmpDir,
Token: token,
}
app := newTestApp(t, cfg)
app.Routes()
ts := newTestServer(t, app.Mux)
defer ts.Close()
code, _, body := ts.delete(t, fmt.Sprintf("/-/package/%s/dist-tags/pre-alpha", url.PathEscape("@df/simplepackone")), token)
assert.Equal(t, code, http.StatusOK)
assert.NotEmpty(t, body)
old := readTestFile(t, indexJsonFp)
oldRet := make(map[string]interface{})
json.Unmarshal(old, &oldRet)
assert.Contains(t, oldRet["dist-tags"], "pre-alpha")
modified := readTestFile(t, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
modifiedRet := make(map[string]interface{})
json.Unmarshal(modified, &modifiedRet)
assert.NotContains(t, modifiedRet["dist-tags"], "pre-alpha")
}

View File

@ -14,7 +14,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func DistTagGet(lg *logrus.Logger, cfg config.Config) http.HandlerFunc { func DistTagGet(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
escapedName := mux.Vars(r)["name"] escapedName := mux.Vars(r)["name"]
packageName, _ := url.PathUnescape(escapedName) packageName, _ := url.PathUnescape(escapedName)
@ -22,7 +22,7 @@ func DistTagGet(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
"function": "dist-tags-get", "function": "dist-tags-get",
}).Debugf("Package name => %s\n", packageName) }).Debugf("Package name => %s\n", packageName)
fileToServe, found, err := storage.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg) fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -35,7 +35,7 @@ func DistTagGet(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
} }
var jsonFile serviceidos.IndexJson var jsonFile serviceidos.IndexJson
err = storage.ReadIndexJson(fileToServe, &jsonFile, lg) err = stg.ReadIndexJson(fileToServe, &jsonFile, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -0,0 +1,255 @@
package handler
import (
"bytes"
"encoding/json"
"fmt"
"gosimplenpm/internal/config"
"gosimplenpm/internal/serviceidos"
"gosimplenpm/internal/storage"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestUnitTagGet(t *testing.T) {
t.Run("return `Internal Server` error if index json cannot be retrieved", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/-/package/{name}/dist-tags", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", false, fmt.Errorf("Filesystem error")
}
tag := "1.2.4"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagGet(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Filesystem error\n")
})
t.Run("return `Not Found` error if package cannot be retrieved", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/-/package/{name}/dist-tags", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", false, nil
}
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
}
req = mux.SetURLVars(req, vars)
DistTagGet(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusNotFound)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Package not found: test-package\n")
})
t.Run("return `Internal Server` error if package.json cannot be parsed", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return fmt.Errorf("Parsing failed")
}
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
}
req = mux.SetURLVars(req, vars)
DistTagGet(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Parsing failed\n")
})
t.Run("return 200 OK", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/-/package/{name}/dist-tags", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
temp := serviceidos.IndexJson{
DistTags: map[string]string{
"current": "1.2.0",
"latest": "1.2.0",
},
}
*res = temp
return nil
}
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
}
req = mux.SetURLVars(req, vars)
DistTagGet(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusOK)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "{\"current\":\"1.2.0\",\"latest\":\"1.2.0\"}")
})
}
func TestIntegrationTagGet(t *testing.T) {
if testing.Short() {
t.Skip("Skipping getTags integration test")
}
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
tmpDir := t.TempDir()
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
mkDir(t, fmt.Sprintf("%s/output", tmpDir))
indexJsonFp := "intestdata/tags/index.json"
tgzFp := "intestdata/tags/simplepackone-1.9.0.tgz"
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.9.0.tgz", tmpDir))
cfg := config.Config{
RepoDir: tmpDir,
Token: token,
}
app := newTestApp(t, cfg)
app.Routes()
ts := newTestServer(t, app.Mux)
defer ts.Close()
code, _, body := ts.get(t, fmt.Sprintf("/-/package/%s/dist-tags", url.PathEscape("@df/simplepackone")))
assert.Equal(t, code, http.StatusOK)
ret := make(map[string]interface{})
json.Unmarshal(body, &ret)
assert.Contains(t, ret, "latest")
assert.Equal(t, "1.9.0", ret["latest"])
}

View File

@ -16,7 +16,7 @@ import (
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
) )
func DistTagPut(lg *logrus.Logger, cfg config.Config) http.HandlerFunc { func DistTagPut(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
escapedName := mux.Vars(r)["name"] escapedName := mux.Vars(r)["name"]
packageName, _ := url.PathUnescape(escapedName) packageName, _ := url.PathUnescape(escapedName)
@ -31,12 +31,12 @@ func DistTagPut(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
}).Debugf("Tag => %s\n", tag) }).Debugf("Tag => %s\n", tag)
if semver.IsValid(tag) { if semver.IsValid(tag) {
http.Error(w, "Tag cannot be a semver version", http.StatusBadRequest) http.Error(w, fmt.Sprintf("Tag %s cannot be a semver version", tag), http.StatusBadRequest)
return return
} }
if tag == "latest" { if tag == "latest" {
http.Error(w, "Cannot delete the latest tag", http.StatusBadRequest) http.Error(w, "Cannot modify the latest tag", http.StatusBadRequest)
return return
} }
@ -47,7 +47,7 @@ func DistTagPut(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
"function": "dist-tags-put", "function": "dist-tags-put",
}).Debugf("Body => %s", version) }).Debugf("Body => %s", version)
fileToServe, found, err := storage.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg) fileToServe, found, err := stg.GetIndexJsonFromStore(packageName, cfg.RepoDir, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -60,7 +60,7 @@ func DistTagPut(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
} }
var jsonFile serviceidos.IndexJson var jsonFile serviceidos.IndexJson
err = storage.ReadIndexJson(fileToServe, &jsonFile, lg) err = stg.ReadIndexJson(fileToServe, &jsonFile, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -69,7 +69,7 @@ func DistTagPut(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
jsonFile.DistTags[tag] = version jsonFile.DistTags[tag] = version
// Write index.json // Write index.json
err = storage.WriteIndexJson(fileToServe, &jsonFile, lg) err = stg.WriteIndexJson(fileToServe, &jsonFile, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -0,0 +1,436 @@
package handler
import (
"bytes"
"encoding/json"
"fmt"
"gosimplenpm/internal/config"
"gosimplenpm/internal/serviceidos"
"gosimplenpm/internal/storage"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestUnitTagPut(t *testing.T) {
t.Run("return `Bad request` error if tag is semver", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
tag := "v1.0.0"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagPut(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), fmt.Sprintf("Tag %s cannot be a semver version\n", tag))
})
t.Run("return `Bad request` error if tag is latest", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
tag := "latest"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagPut(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusBadRequest)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Cannot modify the latest tag\n")
})
t.Run("return `Internal Server` error if index json cannot be retrieved", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("development"))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", false, fmt.Errorf("Filesystem error")
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagPut(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Filesystem error\n")
})
t.Run("return `Not found` error if package cannot be retrieved", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("development"))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", false, nil
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagPut(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusNotFound)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Package not found: test-package\n")
})
t.Run("return `Internal Server` error if package.json cannot be parsed", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("development"))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return fmt.Errorf("Parsing failed")
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagPut(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Parsing failed\n")
})
t.Run("return `Internal Server` error if package.json cannot be written", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("\"3.5.6-rc\""))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
temp := serviceidos.IndexJson{
DistTags: map[string]string{
"current": "1.2.0",
"latest": "1.2.0",
},
}
*res = temp
return nil
}
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return fmt.Errorf("File cannot be written")
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagPut(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "File cannot be written\n")
})
t.Run("return 200 OK if tag can be overwritten", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("\"3.5.6-rc\""))
// req := httptest.NewRequest(http.MethodPut, "/-/package/{name}/dist-tags/{tag}", strings.NewReader("{\"version\":\"development\"}"))
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetIndexJsonFromStoreFunc = func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
return "", true, nil
}
mfs.ReadIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
temp := serviceidos.IndexJson{
DistTags: map[string]string{
"current": "1.2.0",
"latest": "1.2.0",
},
}
*res = temp
return nil
}
mfs.WriteIndexJsonFunc = func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
return nil
}
tag := "current"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "test-package",
"tag": tag,
}
req = mux.SetURLVars(req, vars)
DistTagPut(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusOK)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "{\"ok\":true,\"id\":\"current\",\"dist-tags\":\"{\\\"current\\\":\\\"3.5.6-rc\\\",\\\"latest\\\":\\\"1.2.0\\\"}\"}")
})
}
func TestIntegrationTagPut(t *testing.T) {
if testing.Short() {
t.Skip("Skipping putTags integration test")
}
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
tmpDir := t.TempDir()
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
mkDir(t, fmt.Sprintf("%s/output", tmpDir))
indexJsonFp := "intestdata/tags/index.json"
oldTgzFp := "intestdata/tags/simplepackone-1.0.0.tgz"
tgzFp := "intestdata/tags/simplepackone-1.9.0.tgz"
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
cpFile(t, oldTgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.9.0.tgz", tmpDir))
cfg := config.Config{
RepoDir: tmpDir,
Token: token,
}
app := newTestApp(t, cfg)
app.Routes()
ts := newTestServer(t, app.Mux)
defer ts.Close()
code, _, body := ts.put(t, fmt.Sprintf("/-/package/%s/dist-tags/release-candidate", url.PathEscape("@df/simplepackone")), token, "\"1.0.0\"")
assert.Equal(t, code, http.StatusOK)
assert.NotEmpty(t, body)
old := readTestFile(t, indexJsonFp)
oldRet := make(map[string]interface{})
json.Unmarshal(old, &oldRet)
assert.NotContains(t, oldRet["dist-tags"], "release-candidate")
modified := readTestFile(t, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
modifiedRet := make(map[string]interface{})
json.Unmarshal(modified, &modifiedRet)
assert.Contains(t, modifiedRet["dist-tags"], "release-candidate")
version := modifiedRet["dist-tags"].(map[string]interface{})
assert.Equal(t, version["release-candidate"], "1.0.0")
}

View File

@ -2,6 +2,7 @@ package handler
import ( import (
"bytes" "bytes"
"fmt"
"gosimplenpm/internal/config" "gosimplenpm/internal/config"
"gosimplenpm/internal/storage" "gosimplenpm/internal/storage"
"io" "io"
@ -14,7 +15,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func PackageTarGet(lg *logrus.Logger, cfg config.Config) http.HandlerFunc { func PackageTarGet(lg *logrus.Logger, cfg config.Config, stg storage.Storage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// Sample output of npm view // Sample output of npm view
// Public // Public
@ -30,13 +31,20 @@ func PackageTarGet(lg *logrus.Logger, cfg config.Config) http.HandlerFunc {
"function": "get-tar", "function": "get-tar",
}).Debugf("Package name => %s\n", packageName) }).Debugf("Package name => %s\n", packageName)
escapedName = mux.Vars(r)["tar"] escapedName = mux.Vars(r)["tar"]
tarFileName, _ := url.PathUnescape(escapedName) tarFileNameWithScope, _ := url.PathUnescape(escapedName)
lg.WithFields(logrus.Fields{ lg.WithFields(logrus.Fields{
"function": "get-tar", "function": "get-tar",
}).Debugf("Tarfile name => %s\n", tarFileName) }).Debugf("Tarfile name => %s\n", tarFileNameWithScope)
versionName := strings.Split(strings.Split(tarFileName, "-")[1], ".tgz")[0] fmt.Printf("Tarfile name => %s\n", tarFileNameWithScope)
fileAsString, err := storage.GetTarFromStore(packageName, versionName, cfg.RepoDir, lg)
fragments := strings.Split(tarFileNameWithScope, "/")
tarFileName := fragments[len(fragments)-1]
// fragments := strings.Split(urlFileFragment, "-")
// versionFragment := fragments[len(fragments)-1]
// versionName := strings.Split(versionFragment, ".tgz")[0]
// fmt.Printf("Version name => %s\n", versionName)
fileAsString, err := stg.GetTarFromStore(packageName, tarFileName, cfg.RepoDir, lg)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -0,0 +1,159 @@
package handler
import (
"bytes"
"fmt"
"gosimplenpm/internal/config"
"gosimplenpm/internal/storage"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestUnitTar(t *testing.T) {
t.Run("return `Bad request` error if tar package cannot be read from the filesystem", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/{name}/-/{tar}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetTarFromStoreFunc = func(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error) {
return "", fmt.Errorf("Filesystem error")
}
tag := "@test%2Fpackage-1.2.0.tgz"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "@test%2Fpackage",
"tar": tag,
}
req = mux.SetURLVars(req, vars)
PackageTarGet(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusInternalServerError)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Filesystem error\n")
})
t.Run("return 200 OK if tar is found on the filesystem", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/{name}/-/{tar}", nil)
wrt := httptest.NewRecorder()
lg := &logrus.Logger{
Out: os.Stdout,
// Level: "DEBUG",
Formatter: &logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2009-01-02 15:15:15",
},
}
cfg := config.Config{
RepoDir: "",
}
mfs := &storage.MockFs{}
mfs.GetTarFromStoreFunc = func(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error) {
return "OHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZ", nil
}
tag := "@test%2Fpackage-1.2.0.tgz"
//Hack to try to fake gorilla/mux vars
vars := map[string]string{
"name": "@test%2Fpackage",
"tar": tag,
}
req = mux.SetURLVars(req, vars)
PackageTarGet(lg, cfg, mfs)(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusOK)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "OHnFFeCPAnb7E0jRLSuw4hVrNDVdDmKB4lbye6oZoBVItuRKy6ee43yAMaO6k0yhr2SU9HqWSZ")
})
}
func TestIntegrationTar(t *testing.T) {
if testing.Short() {
t.Skip("Skipping getTar integration test")
}
token := "0N89nr/hmKXoBzG]R{fKH%YE1X"
tmpDir := t.TempDir()
t.Logf("Temp Dir: %s", tmpDir)
// cpFolders(t, "intestdata/@df", fmt.Sprintf("%s/@df", tmpDir))
mkDir(t, fmt.Sprintf("%s/@df/simplepackone", tmpDir))
mkDir(t, fmt.Sprintf("%s/output", tmpDir))
indexJsonFp := "intestdata/tar/index.json"
tgzFp := "intestdata/tar/simplepackone-1.0.0.tgz"
cpFile(t, indexJsonFp, fmt.Sprintf("%s/@df/simplepackone/index.json", tmpDir))
cpFile(t, tgzFp, fmt.Sprintf("%s/@df/simplepackone/simplepackone-1.0.0.tgz", tmpDir))
cfg := config.Config{
RepoDir: tmpDir,
Token: token,
}
app := newTestApp(t, cfg)
app.Routes()
ts := newTestServer(t, app.Mux)
defer ts.Close()
tarUrlFragment := url.PathEscape("@df/simplepackone-1.0.0.tgz")
nameUrlFragment := url.PathEscape("@df/simplepackone")
code, _, body := ts.get(t, fmt.Sprintf("/%s/-/%s", nameUrlFragment, tarUrlFragment))
assert.Equal(t, code, http.StatusOK)
// assert.NotEmpty(t, body)
expected := readTestFileAsBase64(t, tgzFp)
assert.Equal(t, string(body), expected)
}

View File

@ -0,0 +1,231 @@
package handler
import (
"bytes"
"encoding/base64"
"fmt"
"gosimplenpm/internal/config"
"gosimplenpm/internal/storage"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/sirupsen/logrus"
)
func newTestApp(t *testing.T, cfg config.Config) *Application {
return &Application{
Conf: cfg,
Logger: &logrus.Logger{
Out: io.Discard,
// Level: logrus.DebugLevel,
// Formatter: &logrus.TextFormatter{
// FullTimestamp: true,
// TimestampFormat: "2009-01-02 15:15:15",
// },
},
FSStorage: &storage.FSStorage{},
}
}
type testServer struct {
*httptest.Server
}
func newTestServer(t *testing.T, h http.Handler) *testServer {
ts := httptest.NewServer(h)
return &testServer{ts}
}
func (ts *testServer) get(t *testing.T, urlPath string) (int, http.Header, []byte) {
rs, err := ts.Client().Get(ts.URL + urlPath)
if err != nil {
t.Fatal(err)
}
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
body = bytes.TrimSpace(body)
return rs.StatusCode, rs.Header, body
}
func (ts *testServer) put(t *testing.T, urlPath string, token string, data string) (int, http.Header, []byte) {
req, err := http.NewRequest(http.MethodPut, ts.URL+urlPath, strings.NewReader(data))
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
if err != nil {
t.Fatal(err)
}
rs, err := ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
body = bytes.TrimSpace(body)
return rs.StatusCode, rs.Header, body
}
func (ts *testServer) delete(t *testing.T, urlPath string, token string) (int, http.Header, []byte) {
req, err := http.NewRequest(http.MethodDelete, ts.URL+urlPath, nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
if err != nil {
t.Fatal(err)
}
rs, err := ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
body = bytes.TrimSpace(body)
return rs.StatusCode, rs.Header, body
}
func cpFile(t *testing.T, src string, dst string) {
var srcfd *os.File
var dstfd *os.File
var err error
var srcinfo os.FileInfo
srcfd, err = os.Open(src)
if err != nil {
t.Fatal(err)
}
defer srcfd.Close()
dstfd, err = os.Create(dst)
if err != nil {
t.Fatal(err)
}
defer dstfd.Close()
_, err = io.Copy(dstfd, srcfd)
if err != nil {
t.Fatal(err)
}
srcinfo, err = os.Stat(src)
if err != nil {
t.Fatal(err)
}
err = os.Chmod(dst, srcinfo.Mode())
if err != nil {
t.Fatal(err)
}
}
// cpFolders - Copy folders recursively
// func cpFolders(t *testing.T, src string, dst string) {
// var err error
// var fds []fs.DirEntry
// var srcinfo os.FileInfo
// srcinfo, err = os.Stat(src)
// if err != nil {
// t.Fatal(err)
// }
// err = os.MkdirAll(dst, srcinfo.Mode())
// if err != nil {
// t.Fatal(err)
// }
// fds, err = os.ReadDir(src)
// if err != nil {
// t.Fatal(err)
// }
// for _, fd := range fds {
// srcfp := path.Join(src, fd.Name())
// dstfp := path.Join(dst, fd.Name())
// if fd.IsDir() {
// cpFolders(t, srcfp, dstfp)
// } else {
// cpFile(t, srcfp, dstfp)
// }
// }
// }
func mkDir(t *testing.T, fp string) {
err := os.MkdirAll(fp, os.ModePerm)
if err != nil {
t.Fatal(err)
}
}
func readTestFile(t *testing.T, fp string) []byte {
f, err := os.ReadFile(fp)
if err != nil {
t.Fatal(err)
}
return f
}
func readTestFileAsBase64(t *testing.T, fp string) string {
f, err := os.ReadFile(fp)
if err != nil {
t.Fatal(err)
}
return base64.StdEncoding.EncodeToString(f)
}
func listDir(t *testing.T, fp string, list bool) []string {
var filePaths []string
err := filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
if list {
t.Logf("File (Directory: %t)=> %s", info.IsDir(), path)
} else {
if !info.IsDir() {
filePaths = append(filePaths, path)
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
return filePaths
}
func IsDirEmpty(t *testing.T, name string) bool {
f, err := os.Open(name)
if err != nil {
t.Fatal(err)
}
defer f.Close()
// read in ONLY one file
_, err = f.Readdir(1)
// and if the file is EOF... well, the dir is empty.
return err == io.EOF
}
func containsSub(s []string, str string) bool {
for _, v := range s {
if strings.Contains(v, str) {
return true
}
}
return false
}

View File

@ -0,0 +1,99 @@
package middlewares
import (
"bytes"
"gosimplenpm/internal/config"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
)
func TestUnitMAuthMiddleware(t *testing.T) {
router := mux.NewRouter()
handlerStr := []byte("Logic\n")
hFunc := func(w http.ResponseWriter, e *http.Request) {
_, err := w.Write(handlerStr)
if err != nil {
t.Fatalf("Failed writing HTTP response: %v", err)
}
}
cfg := config.Config{
RepoDir: "",
Token: "MyToken",
}
router.HandleFunc("/", AuthMiddleware(cfg)(hFunc))
t.Run("return `Status Foribben` if there is no token", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
wrt := httptest.NewRecorder()
req.Header.Set("Authorization", "")
router.ServeHTTP(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusForbidden)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Authentication Error\n")
})
t.Run("return `Status Foribben` if the Authorization field is not set properly", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
wrt := httptest.NewRecorder()
req.Header.Set("Authorization", "Secret other")
router.ServeHTTP(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusForbidden)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Authentication Error\n")
})
t.Run("return `Status Foribben` if the token is incorrect", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
wrt := httptest.NewRecorder()
req.Header.Set("Authorization", "Bearer incorrectToken")
router.ServeHTTP(wrt, req)
rs := wrt.Result()
assert.Equal(t, rs.StatusCode, http.StatusForbidden)
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
assert.Equal(t, string(body), "Authentication Error\n")
})
}

View File

@ -7,38 +7,97 @@ type IndexJsonAttachments struct {
} }
type IndexJsonDist struct { type IndexJsonDist struct {
Integrity string `json:"integrity"` Integrity string `json:"integrity"`
Shasum string `json:"shasum"` Shasum string `json:"shasum"`
Tarball string `json:"tarball"` Tarball string `json:"tarball"`
FileCount int `json:"fileCount"`
UnpackedSize int `json:"unpackedSize"`
} }
type IndexJsonRepository struct {
Type string `json:"type"`
Url string `json:"url"`
// Only used if the package.json is not in the root folder of the url
Directory string `json:"directory"`
}
type IndexJsonEngines struct {
NodeVersion string `json:"node,omitempty"`
NpmVersion string `json:"npm,omitempty"`
}
type IndexJsonBugs struct {
Url string `json:"url,omitempty"`
Email string `json:"email,omitempty"`
}
type IndexJsonAuthor struct {
Url string `json:"url,omitempty"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
}
// https://docs.npmjs.com/cli/v10/configuring-npm/package-json
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
type IndexJsonVersions struct { type IndexJsonVersions struct {
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
Description string `json:"description"` Description string `json:"description"`
Main string `json:"main,omitempty"` Main string `json:"main,omitempty"`
Scripts map[string]string `json:"scripts,omitempty"` Scripts map[string]string `json:"scripts,omitempty"`
Author string `json:"author,omitempty"` Author string `json:"author,omitempty"`
License string `json:"license"` License string `json:"license"`
Files []string `json:"files"` Repository IndexJsonRepository `json:"repository,omitempty"`
Readme string `json:"readme,omitempty"` Files []string `json:"files,omitempty"`
ID string `json:"_id"` Homepage string `json:"homepage,omitempty"`
NodeVersion string `json:"_nodeVersion"` Readme string `json:"readme,omitempty"`
NpmVersion string `json:"_npmVersion"` ReadmeFilename string `json:"readmeFilename,omitempty"`
Dist IndexJsonDist `json:"dist"` Keywords []string `json:"keywords,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"` ID string `json:"_id"`
DevDependencies map[string]string `json:"devDependencies,omitempty"` Contributors []IndexJsonAuthor `json:"contributors,omitempty"`
Resolutions map[string]string `json:"resolutions,omitempty"` Maintainers []IndexJsonAuthor `json:"maintainers.omitempty"`
Bugs IndexJsonBugs `json:"bugs,omitempty"`
Bin map[string]string `json:"bin"`
OperatingSystem []string `json:"os,omitempty"`
Cpu []string `json:"cpu,omitempty"`
Engines IndexJsonEngines `json:"engines,omitempty"`
NodeVersion string `json:"_nodeVersion"`
NpmVersion string `json:"_npmVersion"`
Dist IndexJsonDist `json:"dist"`
Dependencies map[string]string `json:"dependencies,omitempty"`
DevDependencies map[string]string `json:"devDependencies,omitempty"`
PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
PeerDependenciesMeta map[string]map[string]bool `json:"peerDependenciesMeta,omitempty"`
BundleDependencies []string `json:"bundleDependencies,omitempty"`
Resolutions map[string]string `json:"resolutions,omitempty"`
} }
type IndexJson struct { type IndexJson struct {
ID string `json:"_id"` ID string `json:"_id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
DistTags map[string]string `json:"dist-tags"` Readme string `json:"readme,omitempty"`
Versions map[string]IndexJsonVersions `json:"versions"` ReadmeFilename string `json:"readmeFilename,omitempty"`
Access string `json:"access"` DistTags map[string]string `json:"dist-tags"`
Attachments map[string]IndexJsonAttachments `json:"_attachments"` TimesPackage map[string]string `json:"time"`
Versions map[string]IndexJsonVersions `json:"versions"`
Access string `json:"access"`
Attachments map[string]IndexJsonAttachments `json:"_attachments"`
}
type IndexJsonAbridgedVersions struct {
HasShrinkWrap bool `json:"_hasShrinkwrap"`
Dist IndexJsonDist `json:"dist"`
Name string `json:"name"`
Version string `json:"version"`
}
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
type IndexJsonAbridged struct {
DistTags map[string]string `json:"dist-tags"`
Modified string `json:"modified"`
Name string `json:"name"`
Versions map[string]IndexJsonAbridgedVersions `json:"versions"`
} }
type TagPutResponse struct { type TagPutResponse struct {
@ -52,3 +111,8 @@ type TagDeleteResponse struct {
ID string `json:"id"` ID string `json:"id"`
DistTags string `json:"dist-tags"` DistTags string `json:"dist-tags"`
} }
type PublishPutResponse struct {
Ok bool `json:"ok"`
Name string `json:"package_name"`
}

View File

@ -1,8 +1,6 @@
package storage package storage
import ( import (
"archive/tar"
"compress/gzip"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -18,7 +16,9 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func GetIndexJsonFromStore(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) { type FSStorage struct{}
func (f *FSStorage) GetIndexJsonFromStore(packageName string, registryPath string, log *logrus.Logger) (string, bool, error) {
fileToServe := "" fileToServe := ""
found := false found := false
@ -33,7 +33,7 @@ func GetIndexJsonFromStore(packageName string, registryPath string, log *logrus.
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "get-index-json-from-store", "function": "get-index-json-from-store",
}).Debugf("List files error: +%v\n", err) }).Errorf("List files error: +%v\n", err)
return fileToServe, found, err return fileToServe, found, err
} }
@ -44,7 +44,7 @@ func GetIndexJsonFromStore(packageName string, registryPath string, log *logrus.
return fileToServe, found, nil return fileToServe, found, nil
} }
func GetTarFromStore(packageName string, tarFileName string, registryPath string, log *logrus.Logger) (string, error) { func (f *FSStorage) GetTarFromStore(packageName string, tarFileName string, registryPath string, log *logrus.Logger) (string, error) {
fileToServe := "" fileToServe := ""
err := filepath.WalkDir(registryPath, func(fp string, info fs.DirEntry, e error) error { err := filepath.WalkDir(registryPath, func(fp string, info fs.DirEntry, e error) error {
@ -57,7 +57,7 @@ func GetTarFromStore(packageName string, tarFileName string, registryPath string
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "get-tar-from-store", "function": "get-tar-from-store",
}).Debugf("List files error: +%v\n", err) }).Errorf("List files error: +%v\n", err)
return fileToServe, err return fileToServe, err
} }
@ -69,35 +69,28 @@ func GetTarFromStore(packageName string, tarFileName string, registryPath string
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "get-tar-from-store", "function": "get-tar-from-store",
}).Debugf("Open error: %s\n", fileToServe) }).Errorf("Open error: %s\n", fileToServe)
return "", err return "", err
} }
defer file.Close()
archive, err := gzip.NewReader(file) bs, err := io.ReadAll(file)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "get-tar-from-store", "function": "get-tar-from-store",
}).Debugf("Archive Open error: %s\n", fileToServe) }).Errorf("File Read error: %s\n", fileToServe)
return "", err return "", err
} }
tr := tar.NewReader(archive)
bs, err := io.ReadAll(tr)
if err != nil {
log.WithFields(logrus.Fields{
"function": "get-tar-from-store",
}).Debugf("Archive Read error: %s\n", fileToServe)
return "", err
}
return base64.StdEncoding.EncodeToString(bs), err return base64.StdEncoding.EncodeToString(bs), err
} }
func ReadIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger) error { func (f *FSStorage) ReadIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger) error {
jsonFile, err := os.Open(fPath) jsonFile, err := os.Open(fPath)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "read-index-json", "function": "read-index-json",
}).Debugf("File Not found: %s\n", fPath) }).Errorf("File Not found: %s\n", fPath)
return err return err
} }
@ -107,21 +100,21 @@ func ReadIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "read-index-json", "function": "read-index-json",
}).Debugf("Unmarshalerror: %+v\n", err) }).Errorf("Unmarshalerror: %+v\n", err)
return err return err
} }
return nil return nil
} }
func WriteIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger) error { func (f *FSStorage) WriteIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger) error {
// Need to create the directory first // Need to create the directory first
parent := path.Dir(fPath) parent := path.Dir(fPath)
err := os.MkdirAll(parent, os.ModePerm) err := os.MkdirAll(parent, os.ModePerm)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "write-index-json", "function": "write-index-json",
}).Debugf("Folder (%s) creation failed.\n", fPath) }).Errorf("Folder (%s) creation failed.\n", fPath)
return err return err
} }
@ -131,7 +124,7 @@ func WriteIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "write-index-json", "function": "write-index-json",
}).Debugf("Creation error for path(%s): %+v\n ", fPath, err) }).Errorf("Creation error for path(%s): %+v\n ", fPath, err)
return err return err
} }
@ -148,12 +141,12 @@ func WriteIndexJson(fPath string, res *serviceidos.IndexJson, log *logrus.Logger
return nil return nil
} }
func WritePackageToStore(fPath string, data string, log *logrus.Logger) error { func (f *FSStorage) WritePackageToStore(fPath string, data string, log *logrus.Logger) error {
dec, err := base64.StdEncoding.DecodeString(data) dec, err := base64.StdEncoding.DecodeString(data)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "write-package-to-store", "function": "write-package-to-store",
}).Debugf("Base64 Decode error: %+v\n", err) }).Errorf("Base64 Decode error: %+v\n", err)
return err return err
} }
@ -161,7 +154,7 @@ func WritePackageToStore(fPath string, data string, log *logrus.Logger) error {
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "write-package-to-store", "function": "write-package-to-store",
}).Debugf("Creation error: %s\n", fPath) }).Errorf("Creation error: %s\n", fPath)
return err return err
} }
@ -171,7 +164,7 @@ func WritePackageToStore(fPath string, data string, log *logrus.Logger) error {
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "write-package-to-store", "function": "write-package-to-store",
}).Debugf("Write error: %s\n", fPath) }).Errorf("Write error: %s\n", fPath)
return err return err
} }
@ -179,7 +172,7 @@ func WritePackageToStore(fPath string, data string, log *logrus.Logger) error {
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"function": "write-package-to-store", "function": "write-package-to-store",
}).Debugf("Sync error: %s\n", fPath) }).Errorf("Sync error: %s\n", fPath)
return err return err
} }

View File

@ -0,0 +1,56 @@
package storage
import (
"gosimplenpm/internal/serviceidos"
"github.com/sirupsen/logrus"
)
type MockFs struct {
calls map[string]int
GetIndexJsonFromStoreFunc func(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error)
GetTarFromStoreFunc func(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error)
ReadIndexJsonFunc func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error
WriteIndexJsonFunc func(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error
WritePackageToStoreFunc func(fPath string, data string, lg *logrus.Logger) error
}
func (m *MockFs) GetIndexJsonFromStore(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error) {
if len(m.calls) == 0 {
m.calls = make(map[string]int)
}
m.calls["GetIndexJsonFromStore"] += 1
return m.GetIndexJsonFromStoreFunc(packageName, registryPath, lg)
}
func (m *MockFs) GetTarFromStore(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error) {
if len(m.calls) == 0 {
m.calls = make(map[string]int)
}
m.calls["GetTarFromStore"] += 1
return m.GetTarFromStoreFunc(packageName, tarFileName, registryPath, lg)
}
func (m *MockFs) ReadIndexJson(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
if len(m.calls) == 0 {
m.calls = make(map[string]int)
}
m.calls["ReadIndexJson"] += 1
return m.ReadIndexJsonFunc(fPath, res, lg)
}
func (m *MockFs) WriteIndexJson(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error {
if len(m.calls) == 0 {
m.calls = make(map[string]int)
}
m.calls["WriteIndexJson"] += 1
return m.WriteIndexJsonFunc(fPath, res, lg)
}
func (m *MockFs) WritePackageToStore(fPath string, data string, lg *logrus.Logger) error {
if len(m.calls) == 0 {
m.calls = make(map[string]int)
}
m.calls["WritePackageToStore"] += 1
return m.WritePackageToStoreFunc(fPath, data, lg)
}

View File

@ -0,0 +1,15 @@
package storage
import (
"gosimplenpm/internal/serviceidos"
"github.com/sirupsen/logrus"
)
type Storage interface {
GetIndexJsonFromStore(packageName string, registryPath string, lg *logrus.Logger) (string, bool, error)
GetTarFromStore(packageName string, tarFileName string, registryPath string, lg *logrus.Logger) (string, error)
ReadIndexJson(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error
WriteIndexJson(fPath string, res *serviceidos.IndexJson, lg *logrus.Logger) error
WritePackageToStore(fPath string, data string, lg *logrus.Logger) error
}