From d363b64027a80417175044e1092bbc257f04ffec Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 8 Nov 2024 20:25:30 +0000 Subject: [PATCH] fix nested RESOURCE attribute processing Signed-off-by: Doug Davis --- Makefile | 15 +- README.md | 3 +- misc/Dockerfile-dev | 3 + registry/group.go | 1 + registry/httpStuff.go | 164 ++++++++- tests/http_test.go | 768 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 937 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 4958790e..048adc8e 100644 --- a/Makefile +++ b/Makefile @@ -181,14 +181,18 @@ prof: server qtest @go tool pprof -top -cum cpu.prof | sed -n '0,/flat/p;/xreg/p' | more @rm -f cpu.prof mem.prof tests.test -testdev: +devimage: @# See the misc/Dockerfile-dev for more info - @echo "# Make sure mysql isn't running" - -docker rm -f mysql > /dev/null 2>&1 @echo @echo "# Build the dev image" @misc/errOutput docker build -t duglin/xreg-dev --no-cache \ --build-arg GIT_COMMIT=$(GIT_COMMIT) -f misc/Dockerfile-dev . + +testdev: devimage + @# See the misc/Dockerfile-dev for more info + @echo + @echo "# Make sure mysql isn't running" + -docker rm -f mysql > /dev/null 2>&1 @echo @echo "## Build, test and run the server all within the dev image" docker run -ti -v /var/run/docker.sock:/var/run/docker.sock \ @@ -196,11 +200,12 @@ testdev: @echo "## Done! Exit the dev image testing" clean: + @echo @echo "# Cleaning" @rm -f cpu.prof mem.prof - @rm -f server xr + @rm -f server xr xrconform @rm -f .test .image .push @go clean -cache -testcache @-! which k3d > /dev/null || k3d cluster delete xreg > /dev/null 2>&1 @-docker rm -f mysql mysql-client > /dev/null 2>&1 - @docker system prune -f > /dev/null + @-docker system prune -f -a --volumes > /dev/null diff --git a/README.md b/README.md index a11bd728..9403246e 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ TODOs: - add model tests for typemap - just that we can set via full model updates - support the DB vanishing for a while - create an UpdateDefaultVersion func in resource.go to move it from http logic -- support ?export +- support ?export /export - test xref more - support readonly - remove resource.readonly - remove "readonly" attribute from model Resources, add to Resource @@ -138,3 +138,4 @@ TODOs: - add "compatibility" to resources - multi-delete must ignore all attributes except id and epoch - fix init.sql, it's too slow due to latest xref stuff in commit 9c583e7 +- add support for inline=endpoints.* diff --git a/misc/Dockerfile-dev b/misc/Dockerfile-dev index c959d74c..b7af9cb5 100644 --- a/misc/Dockerfile-dev +++ b/misc/Dockerfile-dev @@ -21,5 +21,8 @@ RUN apk add make git bash docker WORKDIR /go/src/github.com/duglin/xreg-github COPY . ./ +# Erase any built files from the COPY so we have a clean env +RUN make clean + # Pull down all of the go packages just so we don't need to do it later RUN go get ./... diff --git a/registry/group.go b/registry/group.go index e5b3353a..c260a4e8 100644 --- a/registry/group.go +++ b/registry/group.go @@ -255,6 +255,7 @@ func (g *Group) UpsertResourceWithObject(rType string, id string, vID string, ob fmt.Errorf("Key %q in attribute %q doesn't "+ "appear to be of type %q", verID, plural, singular) } + _, _, err := r.UpsertVersionWithObject(verID, verObj, addType) if err != nil { return nil, false, err diff --git a/registry/httpStuff.go b/registry/httpStuff.go index 5755d2f5..9b9b03b9 100644 --- a/registry/httpStuff.go +++ b/registry/httpStuff.go @@ -373,15 +373,32 @@ func (pw *PageWriter) Done() { } checked := "" + inlines := "" + inlineCount := 0 + + if pw.Info.IsInlineSet(NewPPP("model").DB()) { + checked = " checked" + } + if len(pw.Info.Parts) == 0 { + inlines += fmt.Sprintf(` +
+ model +
`, inlineCount) + inlineCount++ + } + checked = "" + if pw.Info.IsInlineSet("*") { checked = " checked" } - inlines := ` + inlines += fmt.Sprintf(`
- * (all) -
` + * (all - except 'model') + `, inlineCount) + inlineCount++ + pp, _ := PropPathFromPath(pw.Info.Abstract) - for i, inline := range inlineOptions { + for _, inline := range inlineOptions { checked = "" pInline := MustPropPathFromUI(inline) fullInline := pp.Append(pInline).DB() @@ -391,7 +408,8 @@ func (pw *PageWriter) Done() { inlines += fmt.Sprintf(`
%s -
`, i+1, inline, checked, inline) + `, inlineCount, inline, checked, inline) + inlineCount++ } tmp := pw.Info.BaseURL @@ -1055,6 +1073,12 @@ func HTTPPutPost(info *RequestInfo) error { if len(info.Parts) == 0 { // PUT / + err = ConvertRegistryContents(IncomingObj, info.Registry.Model) + if err != nil { + info.StatusCode = http.StatusBadRequest + return err + } + addType := ADD_UPDATE if method == "PATCH" { addType = ADD_PATCH @@ -1089,6 +1113,12 @@ func HTTPPutPost(info *RequestInfo) error { } for id, obj := range objMap { + err = ConvertGroupContents(obj, info.GroupModel) + if err != nil { + info.StatusCode = http.StatusBadRequest + return err + } + g, _, err := info.Registry.UpsertGroupWithObject(info.GroupType, id, obj, addType, info.HasNested) if err != nil { @@ -1113,6 +1143,12 @@ func HTTPPutPost(info *RequestInfo) error { addType = ADD_PATCH } + err = ConvertGroupContents(IncomingObj, info.GroupModel) + if err != nil { + info.StatusCode = http.StatusBadRequest + return err + } + group, isNew, err := info.Registry.UpsertGroupWithObject(info.GroupType, info.GroupUID, IncomingObj, addType, info.HasNested) if err != nil { @@ -1223,6 +1259,12 @@ func HTTPPutPost(info *RequestInfo) error { "not %q", info.ResourceModel.Singular, resourceUID, propsID) } + err = ConvertResourceContents(IncomingObj, info.ResourceModel) + if err != nil { + info.StatusCode = http.StatusBadRequest + return err + } + if resource != nil { // version, err = resource.GetDefault() @@ -1275,6 +1317,12 @@ func HTTPPutPost(info *RequestInfo) error { } } + err = ConvertVersionContents(IncomingObj, info.ResourceModel) + if err != nil { + info.StatusCode = http.StatusBadRequest + return err + } + if resource == nil { // Implicitly create the resource resource, isNew, err = group.UpsertResourceWithObject( @@ -1314,7 +1362,7 @@ func HTTPPutPost(info *RequestInfo) error { } for _, obj := range objMap { - err = ConvertResourceContents(obj, info.ResourceModel) + err = ConvertVersionContents(obj, info.ResourceModel) if err != nil { info.StatusCode = http.StatusBadRequest return err @@ -1409,6 +1457,12 @@ func HTTPPutPost(info *RequestInfo) error { } } + err = ConvertVersionContents(IncomingObj, info.ResourceModel) + if err != nil { + info.StatusCode = http.StatusBadRequest + return err + } + if resource == nil { // Implicitly create the resource resource, err = group.AddResourceWithObject(info.ResourceType, @@ -2068,11 +2122,6 @@ func ExtractIncomingObject(info *RequestInfo, body []byte) (Object, error) { info.StatusCode = http.StatusBadRequest return nil, err } - - err = ConvertResourceContents(IncomingObj, info.ResourceModel) - if err != nil { - return nil, err - } } // xReg metadata are in headers, so move them into IncomingObj. We'll @@ -2198,13 +2247,101 @@ func ExtractIncomingObject(info *RequestInfo, body []byte) (Object, error) { return IncomingObj, nil } +// Find all Groups/Resources/Versions in 'obj' and make sure their +// RESOURCE attribute is converted into a byte array +func ConvertRegistryContents(obj map[string]any, m *Model) error { + for _, gm := range m.Groups { + val, ok := obj[gm.Plural] + if ok && !IsNil(val) { + gColl, ok := val.(map[string]any) + if !ok { + return fmt.Errorf("%q should be a map", gm.Plural) + } + for _, val := range gColl { + g, ok := val.(map[string]any) + if !ok { + return fmt.Errorf("%q should be a map of %q, not %T", + gm.Plural, gm.Singular, val) + } + // Traverse into each Group + if err := ConvertGroupContents(g, gm); err != nil { + return err + } + } + } + } + return nil +} + +// Find all Resources/Versions in 'obj' and make sure their +// RESOURCE attribute is converted into a byte array +func ConvertGroupContents(obj map[string]any, gm *GroupModel) error { + for _, rm := range gm.Resources { + val, ok := obj[rm.Plural] + if ok && !IsNil(val) { + rColl, ok := val.(map[string]any) + if !ok { + return fmt.Errorf("%q should be a map", rm.Plural) + } + for _, val := range rColl { + r, ok := val.(map[string]any) + if !ok { + return fmt.Errorf("%s should be a map of %q, not %T", + rm.Plural, rm.Singular, val) + } + // Traverse into each Resource + if err := ConvertResourceContents(r, rm); err != nil { + return err + } + } + } + } + return nil +} + +// Convert the RESOURCE attribute from JSON to a byte array, if present. +// Then do any Versions in the "versions" collection, if there. func ConvertResourceContents(obj map[string]any, rm *ResourceModel) error { if rm == nil || rm.GetHasDocument() == false { return nil } + // Process the local RESOURCE attribute before we look for "versions" + if err := ConvertVersionContents(obj, rm); err != nil { + return err + } + + val, ok := obj["versions"] + if ok && !IsNil(val) { + vColl, ok := val.(map[string]any) + if !ok { + return fmt.Errorf("%q should be a map", "versions") + } + for _, val := range vColl { + v, ok := val.(map[string]any) + if !ok { + return fmt.Errorf("%q should be a map of %q, not %T", + "versions", "version", val) + } + // Traverse into each Version + if err := ConvertVersionContents(v, rm); err != nil { + return err + } + } + } + + return nil +} + +// Convert the RESOURCE attribute from JSON to a byte array, if present. +func ConvertVersionContents(obj map[string]any, rm *ResourceModel) error { + if rm == nil || rm.GetHasDocument() == false { + return nil + } + var err error data, ok := obj[rm.Singular] + if ok { // Special case of: RESOURCE:null @@ -2214,6 +2351,11 @@ func ConvertResourceContents(obj map[string]any, rm *ResourceModel) error { return nil } + // Keep bytes as bytes - possibly already converted (eg was in body) + if reflect.ValueOf(data).Type().String() == "[]uint8" { + return nil + } + // Get the raw bytes of the "rm.Singular" json attribute buf := []byte(nil) switch reflect.ValueOf(data).Kind() { diff --git a/tests/http_test.go b/tests/http_test.go index e6e42f13..b6027b7f 100644 --- a/tests/http_test.go +++ b/tests/http_test.go @@ -12686,3 +12686,771 @@ func TestHTTPVersionIDs(t *testing.T) { }`, 400, "The \"versionid\" attribute must be set to \"v1\", not \"vx\"\n") } + +func TestHTTPRecursiveData(t *testing.T) { + reg := NewRegistry("TestHTTPRecursiveData") + defer PassDeleteReg(t, reg) + xCheck(t, reg != nil, "can't create reg") + + gm, _ := reg.Model.AddGroupModel("dirs", "dir") + gm.AddResourceModelSimple("files", "file") + + // Converting the RESOURCE attributes from JSON into byte array tests + + xHTTP(t, reg, "PATCH", "/?nested", `{ + "dirs": { + "d1": { + "files": { + "f1": { + "file": { "foo": "bar" }, + "versions": { + "v1": { + "file": { "bar": "foo" } + } + } + }, + "f2": { + "file": "string" + }, + "f3": { + "file": 42 + }, + "f4": { + "file": [ "foo" ] + }, + "f5": { + "filebase64": "YmluYXJ5Cg==" + } + } + } + } +}`, 200, + `{ + "specversion": "0.5", + "registryid": "TestHTTPRecursiveData", + "self": "http://localhost:8181/", + "epoch": 2, + "createdat": "2024-11-07T15:53:55.28040091Z", + "modifiedat": "2024-11-07T15:53:55.294594572Z", + + "dirscount": 1, + "dirsurl": "http://localhost:8181/dirs" +} +`) + + xHTTP(t, reg, "GET", "/?inline", ``, 200, `{ + "specversion": "0.5", + "registryid": "TestHTTPRecursiveData", + "self": "http://localhost:8181/", + "epoch": 2, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + + "dirs": { + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + }, + + "defaultversionid": "v1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + } + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions" + }, + "f2": { + "fileid": "f2", + "self": "http://localhost:8181/dirs/d1/files/f2$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": "string", + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + + "versions": { + "1": { + "fileid": "f2", + "versionid": "1", + "self": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": "string" + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f2/versions" + }, + "f3": { + "fileid": "f3", + "self": "http://localhost:8181/dirs/d1/files/f3$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": 42, + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f3/versions/1$structure", + + "versions": { + "1": { + "fileid": "f3", + "versionid": "1", + "self": "http://localhost:8181/dirs/d1/files/f3/versions/1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": 42 + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f3/versions" + }, + "f4": { + "fileid": "f4", + "self": "http://localhost:8181/dirs/d1/files/f4$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": [ + "foo" + ], + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f4/versions/1$structure", + + "versions": { + "1": { + "fileid": "f4", + "versionid": "1", + "self": "http://localhost:8181/dirs/d1/files/f4/versions/1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": [ + "foo" + ] + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f4/versions" + }, + "f5": { + "fileid": "f5", + "self": "http://localhost:8181/dirs/d1/files/f5$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "filebase64": "YmluYXJ5Cg==", + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f5/versions/1$structure", + + "versions": { + "1": { + "fileid": "f5", + "versionid": "1", + "self": "http://localhost:8181/dirs/d1/files/f5/versions/1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "filebase64": "YmluYXJ5Cg==" + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f5/versions" + } + }, + "filescount": 5, + "filesurl": "http://localhost:8181/dirs/d1/files" + } + }, + "dirscount": 1, + "dirsurl": "http://localhost:8181/dirs" +} +`) + + xHTTP(t, reg, "DELETE", "/dirs", ``, 204, ``) + + xHTTP(t, reg, "PATCH", "/?nested", `{ + "dirs": { + "d1": { + "files": { + "f1": { + "file": { "foo": "bar" }, + "versions": { + "v1": { + "file": { "bar": "foo" } + } + } + }, + "f2": { + "file": "string" + } + } + } + } +}`, 200, + `{ + "specversion": "0.5", + "registryid": "TestHTTPRecursiveData", + "self": "http://localhost:8181/", + "epoch": 3, + "createdat": "2024-11-07T15:53:55.28040091Z", + "modifiedat": "2024-11-07T15:53:55.294594572Z", + + "dirscount": 1, + "dirsurl": "http://localhost:8181/dirs" +} +`) + + xHTTP(t, reg, "GET", "/?inline", ``, 200, `{ + "specversion": "0.5", + "registryid": "TestHTTPRecursiveData", + "self": "http://localhost:8181/", + "epoch": 3, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + + "dirs": { + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + }, + + "defaultversionid": "v1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + } + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions" + }, + "f2": { + "fileid": "f2", + "self": "http://localhost:8181/dirs/d1/files/f2$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": "string", + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + + "versions": { + "1": { + "fileid": "f2", + "versionid": "1", + "self": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:02Z", + "modifiedat": "YYYY-MM-DDTHH:MM:02Z", + "contenttype": "application/json", + "file": "string" + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f2/versions" + } + }, + "filescount": 2, + "filesurl": "http://localhost:8181/dirs/d1/files" + } + }, + "dirscount": 1, + "dirsurl": "http://localhost:8181/dirs" +} +`) + + xHTTP(t, reg, "DELETE", "/dirs", ``, 204, ``) + + xHTTP(t, reg, "POST", "/dirs?nested", `{ + "d1": { + "files": { + "f1": { + "file": { "foo": "bar" }, + "versions": { + "v1": { + "file": { "bar": "foo" } + } + } + }, + "f2": { + "file": "string" + } + } + } +}`, 200, + `{ + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + + "filescount": 2, + "filesurl": "http://localhost:8181/dirs/d1/files" + } +} +`) + + xHTTP(t, reg, "GET", "/dirs?inline", ``, 200, `{ + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + }, + + "defaultversionid": "v1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + } + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions" + }, + "f2": { + "fileid": "f2", + "self": "http://localhost:8181/dirs/d1/files/f2$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": "string", + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + + "versions": { + "1": { + "fileid": "f2", + "versionid": "1", + "self": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": "string" + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f2/versions" + } + }, + "filescount": 2, + "filesurl": "http://localhost:8181/dirs/d1/files" + } +} +`) + + xHTTP(t, reg, "DELETE", "/dirs", ``, 204, ``) + + xHTTP(t, reg, "PUT", "/dirs/d1?nested", `{ + "files": { + "f1": { + "file": { "foo": "bar" }, + "versions": { + "v1": { + "file": { "bar": "foo" } + } + } + }, + "f2": { + "file": "string" + } + } +}`, 201, + `{ + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + + "filescount": 2, + "filesurl": "http://localhost:8181/dirs/d1/files" +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1?inline", ``, 200, `{ + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + }, + + "defaultversionid": "v1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + } + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions" + }, + "f2": { + "fileid": "f2", + "self": "http://localhost:8181/dirs/d1/files/f2$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": "string", + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + + "versions": { + "1": { + "fileid": "f2", + "versionid": "1", + "self": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": "string" + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f2/versions" + } + }, + "filescount": 2, + "filesurl": "http://localhost:8181/dirs/d1/files" +} +`) + + xHTTP(t, reg, "DELETE", "/dirs", ``, 204, ``) + + xHTTP(t, reg, "POST", "/dirs/d1/files?nested", `{ + "f1": { + "file": { "foo": "bar" }, + "versions": { + "v1": { + "file": { "bar": "foo" } + } + } + }, + "f2": { + "file": "string" + } +}`, 200, + `{ + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + + "defaultversionid": "v1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions" + }, + "f2": { + "fileid": "f2", + "self": "http://localhost:8181/dirs/d1/files/f2$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f2/versions" + } +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files?inline", ``, 200, `{ + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + }, + + "defaultversionid": "v1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + } + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions" + }, + "f2": { + "fileid": "f2", + "self": "http://localhost:8181/dirs/d1/files/f2$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": "string", + + "defaultversionid": "1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + + "versions": { + "1": { + "fileid": "f2", + "versionid": "1", + "self": "http://localhost:8181/dirs/d1/files/f2/versions/1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": "string" + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f2/versions" + } +} +`) + + xHTTP(t, reg, "DELETE", "/dirs", ``, 204, ``) + + xHTTP(t, reg, "PUT", "/dirs/d1/files/f1$structure?nested", `{ + "file": { "foo": "bar" }, + "versions": { + "v1": { + "file": { "bar": "foo" } + } + } +}`, 201, + `{ + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + + "defaultversionid": "v1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions" +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/f1$structure?inline", ``, 200, `{ + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "epoch": 1, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + }, + + "defaultversionid": "v1", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + } + } + }, + "versionscount": 1, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions" +} +`) + + xHTTP(t, reg, "DELETE", "/dirs", ``, 204, ``) + + xHTTP(t, reg, "POST", "/dirs/d1/files/f1/versions?nested", `{ + "v1": { + "file": { "bar": "foo" } + } +}`, 200, + `{ + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json" + } +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/f1/versions?inline", ``, 200, `{ + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "epoch": 1, + "isdefault": true, + "createdat": "YYYY-MM-DDTHH:MM:01Z", + "modifiedat": "YYYY-MM-DDTHH:MM:01Z", + "contenttype": "application/json", + "file": { + "bar": "foo" + } + } +} +`) + +}