diff --git a/misc/sql b/misc/sql new file mode 100755 index 00000000..2090ecbd --- /dev/null +++ b/misc/sql @@ -0,0 +1,14 @@ +#!/bin/bash + +# Usage: sql [ dbName: registry | testreg ] SQL_CMD +# Don't forget to escape things, e.g. sql select \* from Props + +registry=registry +if [[ "$1" == "registry" || "$1" == "testreg" ]]; then + registry=$1 + shift +fi + +docker run -ti --rm --network host --name mysql-client-cmd mysql mysql \ + --host 127.0.0.1 --user root --password=password --protocol tcp \ + $registry -e "$*" diff --git a/registry/entity.go b/registry/entity.go index c3b83a80..e5c24858 100644 --- a/registry/entity.go +++ b/registry/entity.go @@ -121,7 +121,19 @@ func (e *Entity) GetAsInt(path string) int { } func (e *Entity) GetPP(pp *PropPath) any { - name := pp.DB() + // See if we have an updated value in NewObject, if not grab from Object + var val any + if e.NewObject != nil { + var ok bool + val, ok, _ = ObjectGetProp(e.NewObject, pp) + if !ok { + // TODO: DUG - we should not need this + // val, _, _ = ObjectGetProp(e.Object, pp) + } + } else { + val, _, _ = ObjectGetProp(e.Object, pp) + } + if pp.Len() == 1 && pp.Top() == "#resource" { results, err := Query(e.tx, ` SELECT Content @@ -149,19 +161,6 @@ func (e *Entity) GetPP(pp *PropPath) any { return (*(row[0])).([]byte) } - // See if we have an updated value in NewObject, if not grab from Object - var val any - if e.NewObject != nil { - var ok bool - val, ok, _ = ObjectGetProp(e.NewObject, pp) - if !ok { - // TODO: DUG - we should not need this - // val, _, _ = ObjectGetProp(e.Object, pp) - } - } else { - val, _, _ = ObjectGetProp(e.Object, pp) - } - // We used to just grab from Object, not NewObject /* // An error from ObjectGetProp is ignored because if they tried to @@ -170,7 +169,8 @@ func (e *Entity) GetPP(pp *PropPath) any { // should return the 'error' val, _ , _ := ObjectGetProp(e.Object, pp) */ - log.VPrintf(4, "%s(%s).Get(%s) -> %v", e.Plural, e.UID, name, val) + + log.VPrintf(4, "%s(%s).Get(%s) -> %v", e.Plural, e.UID, pp.DB(), val) return val } @@ -1709,7 +1709,10 @@ func (e *Entity) Save() error { e.RemoveCollections(newObj) - err := Do(e.tx, `DELETE FROM Props WHERE EntitySID=?`, e.DbSID) + // We exclude #resource because we need to leave it around until + // a user action causes us to explictly delete it + err := Do(e.tx, "DELETE FROM Props WHERE EntitySID=? "+ + "AND PropName!='"+NewPPP("#resource").DB()+"'", e.DbSID) if err != nil { return fmt.Errorf("Error deleting all prop: %s", err) } diff --git a/registry/httpStuff.go b/registry/httpStuff.go index f173bc10..fdde1fad 100644 --- a/registry/httpStuff.go +++ b/registry/httpStuff.go @@ -1172,6 +1172,12 @@ func HTTPGet(info *RequestInfo) error { return SerializeQuery(info, nil, "Registry", info.Filters) } + // Might need to check other flags, but if we're exporting, it makes + // no sense to show just the resource contents + if info.HasFlag("export") { + info.ShowStructure = true + } + // 'metaInBody' tells us whether xReg metadata should be in the http // response body or not (meaning, the hasDoc doc) metaInBody := (info.ResourceModel == nil) || @@ -1255,6 +1261,16 @@ func SerializeQuery(info *RequestInfo, paths []string, what string, } } + // Another special case... .../rID/versions?export when rID has xref set + if jw.Entity == nil && info.HasFlag("export") && len(info.Parts) == 5 && info.Parts[4] == "versions" { + // Should be our case since "versions" can never be empty except + // when xref is set. If this is not longer true then we'll need to + // check this Resource's xref to see if it's set. + // Can copy the RawEntityFromPath... stuff above + info.StatusCode = http.StatusNotFound + return fmt.Errorf("Not found") + } + info.AddHeader("Content-Type", "application/json") if what == "Coll" { _, err = jw.WriteCollection() diff --git a/registry/jsonWriter.go b/registry/jsonWriter.go index 2277d19f..dbd10aea 100644 --- a/registry/jsonWriter.go +++ b/registry/jsonWriter.go @@ -274,6 +274,16 @@ func SerializeResourceContents(jw *JsonWriter, e *Entity, info *RequestInfo, ext _, rm := jw.Entity.GetModels() singular := rm.Singular + // If the #resource* props aren't there then just exit. + // This will happen when "export" is enabled because the + // props won't show up in the Resorce but will on the default version + // TODO really should do this check in entity.SerializeProps + if IsNil(jw.Entity.Object["#resourceURL"]) && + IsNil(jw.Entity.Object["#resource"]) && + IsNil(jw.Entity.Object["#resourceProxyURL"]) { + return nil + } + if url := jw.Entity.GetAsString("#resourceURL"); url != "" { jw.Printf("%s\n%s%q: %q", *extra, jw.indent, singular+"url", url) *extra = "," diff --git a/tests/content_test.go b/tests/content_test.go index 4745a6b6..e499aa9e 100644 --- a/tests/content_test.go +++ b/tests/content_test.go @@ -126,6 +126,63 @@ func TestResourceContents(t *testing.T) { }, }) + // v4 = #resourceURL + xHTTP(t, reg, "PATCH", "dirs/d1/files/f1/versions/v4$structure?inline=file", + `{"contenttype":null, "description":"hi"}`, 200, `{ + "fileid": "f1", + "versionid": "v4", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v4$structure", + "xid": "/dirs/d1/files/f1/versions/v4", + "epoch": 2, + "isdefault": true, + "description": "hi", + "createdat": "2025-01-01T12:00:01Z", + "modifiedat": "2025-01-01T12:00:02Z", + "fileurl": "http://example.com" +} +`) + + xHTTP(t, reg, "GET", "dirs/d1/files/f1$structure?export&inline=file", + `{"contenttype":null, "description":"hi"}`, 200, `{ + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versionscount": 4 +} +`) + + // Set default to v2 + xHTTP(t, reg, "PATCH", "dirs/d1/files/f1/meta", + `{"defaultversionid":"v2", "defaultversionsticky":true}`, 200, `{ + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 5, + "createdat": "2025-01-01T12:00:01Z", + "modifiedat": "2025-01-01T12:00:02Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2", + "defaultversionsticky": true +} +`) + + // v2 = #resource + xHTTP(t, reg, "PATCH", "dirs/d1/files/f1$structure?export&inline=file", + `{"contenttype":null, "description":"hi"}`, 200, `{ + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versionscount": 4 +} +`) + } type Test struct { diff --git a/tests/export_test.go b/tests/export_test.go new file mode 100644 index 00000000..e44b892c --- /dev/null +++ b/tests/export_test.go @@ -0,0 +1,1339 @@ +package tests + +import ( + "testing" +) + +func TestExportRoot(t *testing.T) { + reg := NewRegistry("TestExportRoot") + defer PassDeleteReg(t, reg) + + gm, _ := reg.Model.AddGroupModel("dirs", "dir") + gm.AddResourceModel("files", "file", 0, true, true, true) + + xHTTP(t, reg, "PUT", "/dirs/d1/files/f1/versions/v1$structure", + `{"file": { "hello": "world" }}`, 201, `*`) + xHTTP(t, reg, "PUT", "/dirs/d1/files/f1/versions/v2$structure", + `{"file": { "hello": "world" }}`, 201, `*`) + xHTTP(t, reg, "PUT", "/dirs/d1/files/fx$structure", + `{"meta": { "xref": "/dirs/d1/files/f1" }}`, 201, `*`) + + // Full export - 2 different ways + code, fullBody := xGET(t, "export") + xCheckEqual(t, "", code, 200) + + code, manualBody := xGET(t, "?export&inline=*,capabilities,model") + xCheckEqual(t, "", code, 200) + xCheckEqual(t, "", fullBody, manualBody) + + xCheckEqual(t, "", fullBody, `{ + "specversion": "0.5", + "registryid": "TestExportRoot", + "self": "http://localhost:8181/", + "xid": "/", + "epoch": 2, + "createdat": "2025-01-01T12:00:01Z", + "modifiedat": "2025-01-01T12:00:02Z", + + "capabilities": { + "enforcecompatibility": false, + "flags": [ + "epoch", + "export", + "filter", + "inline", + "nested", + "nodefaultversionid", + "nodefaultversionsticky", + "noepoch", + "noreadonly", + "schema", + "setdefaultversionid", + "specversion" + ], + "mutable": [ + "capabilities", + "entities", + "model" + ], + "pagination": false, + "schemas": [ + "xregistry-json/0.5" + ], + "shortself": false, + "specversions": [ + "0.5" + ] + }, + "model": { + "attributes": { + "specversion": { + "name": "specversion", + "type": "string", + "readonly": true, + "immutable": true, + "serverrequired": true + }, + "registryid": { + "name": "registryid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "self": { + "name": "self", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xid": { + "name": "xid", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "epoch": { + "name": "epoch", + "type": "uinteger", + "serverrequired": true + }, + "name": { + "name": "name", + "type": "string" + }, + "description": { + "name": "description", + "type": "string" + }, + "documentation": { + "name": "documentation", + "type": "url" + }, + "labels": { + "name": "labels", + "type": "map", + "item": { + "type": "string" + } + }, + "createdat": { + "name": "createdat", + "type": "timestamp", + "serverrequired": true + }, + "modifiedat": { + "name": "modifiedat", + "type": "timestamp", + "serverrequired": true + } + }, + "groups": { + "dirs": { + "plural": "dirs", + "singular": "dir", + "attributes": { + "dirid": { + "name": "dirid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "self": { + "name": "self", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xid": { + "name": "xid", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "epoch": { + "name": "epoch", + "type": "uinteger", + "serverrequired": true + }, + "name": { + "name": "name", + "type": "string" + }, + "description": { + "name": "description", + "type": "string" + }, + "documentation": { + "name": "documentation", + "type": "url" + }, + "labels": { + "name": "labels", + "type": "map", + "item": { + "type": "string" + } + }, + "createdat": { + "name": "createdat", + "type": "timestamp", + "serverrequired": true + }, + "modifiedat": { + "name": "modifiedat", + "type": "timestamp", + "serverrequired": true + } + }, + "resources": { + "files": { + "plural": "files", + "singular": "file", + "maxversions": 0, + "setversionid": true, + "setdefaultversionsticky": true, + "hasdocument": true, + "attributes": { + "fileid": { + "name": "fileid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "versionid": { + "name": "versionid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "self": { + "name": "self", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xid": { + "name": "xid", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "epoch": { + "name": "epoch", + "type": "uinteger", + "serverrequired": true + }, + "name": { + "name": "name", + "type": "string" + }, + "isdefault": { + "name": "isdefault", + "type": "boolean", + "readonly": true + }, + "description": { + "name": "description", + "type": "string" + }, + "documentation": { + "name": "documentation", + "type": "url" + }, + "labels": { + "name": "labels", + "type": "map", + "item": { + "type": "string" + } + }, + "createdat": { + "name": "createdat", + "type": "timestamp", + "serverrequired": true + }, + "modifiedat": { + "name": "modifiedat", + "type": "timestamp", + "serverrequired": true + }, + "contenttype": { + "name": "contenttype", + "type": "string" + } + }, + "metaattributes": { + "fileid": { + "name": "fileid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "self": { + "name": "self", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xid": { + "name": "xid", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xref": { + "name": "xref", + "type": "url" + }, + "epoch": { + "name": "epoch", + "type": "uinteger", + "serverrequired": true + }, + "createdat": { + "name": "createdat", + "type": "timestamp", + "serverrequired": true + }, + "modifiedat": { + "name": "modifiedat", + "type": "timestamp", + "serverrequired": true + }, + "defaultversionid": { + "name": "defaultversionid", + "type": "string", + "serverrequired": true + }, + "defaultversionurl": { + "name": "defaultversionurl", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "defaultversionsticky": { + "name": "defaultversionsticky", + "type": "boolean", + "readonly": true + } + } + } + } + } + } + }, + + "dirsurl": "http://localhost:8181/dirs", + "dirs": { + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "xid": "/dirs/d1", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:03Z", + + "filesurl": "http://localhost:8181/dirs/d1/files", + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "meta": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:04Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure" + }, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "xid": "/dirs/d1/files/f1/versions/v1", + "epoch": 1, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:02Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + }, + "v2": { + "fileid": "f1", + "versionid": "v2", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure", + "xid": "/dirs/d1/files/f1/versions/v2", + "epoch": 1, + "isdefault": true, + "createdat": "2025-01-01T12:00:04Z", + "modifiedat": "2025-01-01T12:00:04Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + } + }, + "versionscount": 2 + }, + "fx": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx$structure", + "xid": "/dirs/d1/files/fx", + + "metaurl": "http://localhost:8181/dirs/d1/files/fx/meta", + "meta": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" + } + } + }, + "filescount": 2 + } + }, + "dirscount": 1 +} +`) + + // Play with ?export vanilla + code, fullBody = xGET(t, "export?inline=*") + xCheckEqual(t, "", code, 200) + code, manualBody = xGET(t, "?export") + xCheckEqual(t, "", code, 200) + xCheckEqual(t, "", fullBody, manualBody) + + xCheckEqual(t, "", fullBody, `{ + "specversion": "0.5", + "registryid": "TestExportRoot", + "self": "http://localhost:8181/", + "xid": "/", + "epoch": 2, + "createdat": "2025-01-01T12:00:01Z", + "modifiedat": "2025-01-01T12:00:02Z", + + "dirsurl": "http://localhost:8181/dirs", + "dirs": { + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "xid": "/dirs/d1", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:03Z", + + "filesurl": "http://localhost:8181/dirs/d1/files", + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "meta": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:04Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure" + }, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "xid": "/dirs/d1/files/f1/versions/v1", + "epoch": 1, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:02Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + }, + "v2": { + "fileid": "f1", + "versionid": "v2", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure", + "xid": "/dirs/d1/files/f1/versions/v2", + "epoch": 1, + "isdefault": true, + "createdat": "2025-01-01T12:00:04Z", + "modifiedat": "2025-01-01T12:00:04Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + } + }, + "versionscount": 2 + }, + "fx": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx$structure", + "xid": "/dirs/d1/files/fx", + + "metaurl": "http://localhost:8181/dirs/d1/files/fx/meta", + "meta": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" + } + } + }, + "filescount": 2 + } + }, + "dirscount": 1 +} +`) + + // Play with ?export inline just capabilities + code, fullBody = xGET(t, "export?inline=capabilities") + xCheckEqual(t, "", code, 200) + code, manualBody = xGET(t, "?export&inline=capabilities") + xCheckEqual(t, "", code, 200) + xCheckEqual(t, "", fullBody, manualBody) + + xCheckEqual(t, "", fullBody, `{ + "specversion": "0.5", + "registryid": "TestExportRoot", + "self": "http://localhost:8181/", + "xid": "/", + "epoch": 2, + "createdat": "2025-01-01T12:00:01Z", + "modifiedat": "2025-01-01T12:00:02Z", + + "capabilities": { + "enforcecompatibility": false, + "flags": [ + "epoch", + "export", + "filter", + "inline", + "nested", + "nodefaultversionid", + "nodefaultversionsticky", + "noepoch", + "noreadonly", + "schema", + "setdefaultversionid", + "specversion" + ], + "mutable": [ + "capabilities", + "entities", + "model" + ], + "pagination": false, + "schemas": [ + "xregistry-json/0.5" + ], + "shortself": false, + "specversions": [ + "0.5" + ] + }, + + "dirsurl": "http://localhost:8181/dirs", + "dirscount": 1 +} +`) + + // Play with ?export inline just model + code, fullBody = xGET(t, "export?inline=model") + xCheckEqual(t, "", code, 200) + code, manualBody = xGET(t, "?export&inline=model") + xCheckEqual(t, "", code, 200) + xCheckEqual(t, "", fullBody, manualBody) + + xCheckEqual(t, "", fullBody, `{ + "specversion": "0.5", + "registryid": "TestExportRoot", + "self": "http://localhost:8181/", + "xid": "/", + "epoch": 2, + "createdat": "2025-01-01T12:00:01Z", + "modifiedat": "2025-01-01T12:00:02Z", + + "model": { + "attributes": { + "specversion": { + "name": "specversion", + "type": "string", + "readonly": true, + "immutable": true, + "serverrequired": true + }, + "registryid": { + "name": "registryid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "self": { + "name": "self", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xid": { + "name": "xid", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "epoch": { + "name": "epoch", + "type": "uinteger", + "serverrequired": true + }, + "name": { + "name": "name", + "type": "string" + }, + "description": { + "name": "description", + "type": "string" + }, + "documentation": { + "name": "documentation", + "type": "url" + }, + "labels": { + "name": "labels", + "type": "map", + "item": { + "type": "string" + } + }, + "createdat": { + "name": "createdat", + "type": "timestamp", + "serverrequired": true + }, + "modifiedat": { + "name": "modifiedat", + "type": "timestamp", + "serverrequired": true + } + }, + "groups": { + "dirs": { + "plural": "dirs", + "singular": "dir", + "attributes": { + "dirid": { + "name": "dirid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "self": { + "name": "self", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xid": { + "name": "xid", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "epoch": { + "name": "epoch", + "type": "uinteger", + "serverrequired": true + }, + "name": { + "name": "name", + "type": "string" + }, + "description": { + "name": "description", + "type": "string" + }, + "documentation": { + "name": "documentation", + "type": "url" + }, + "labels": { + "name": "labels", + "type": "map", + "item": { + "type": "string" + } + }, + "createdat": { + "name": "createdat", + "type": "timestamp", + "serverrequired": true + }, + "modifiedat": { + "name": "modifiedat", + "type": "timestamp", + "serverrequired": true + } + }, + "resources": { + "files": { + "plural": "files", + "singular": "file", + "maxversions": 0, + "setversionid": true, + "setdefaultversionsticky": true, + "hasdocument": true, + "attributes": { + "fileid": { + "name": "fileid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "versionid": { + "name": "versionid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "self": { + "name": "self", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xid": { + "name": "xid", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "epoch": { + "name": "epoch", + "type": "uinteger", + "serverrequired": true + }, + "name": { + "name": "name", + "type": "string" + }, + "isdefault": { + "name": "isdefault", + "type": "boolean", + "readonly": true + }, + "description": { + "name": "description", + "type": "string" + }, + "documentation": { + "name": "documentation", + "type": "url" + }, + "labels": { + "name": "labels", + "type": "map", + "item": { + "type": "string" + } + }, + "createdat": { + "name": "createdat", + "type": "timestamp", + "serverrequired": true + }, + "modifiedat": { + "name": "modifiedat", + "type": "timestamp", + "serverrequired": true + }, + "contenttype": { + "name": "contenttype", + "type": "string" + } + }, + "metaattributes": { + "fileid": { + "name": "fileid", + "type": "string", + "immutable": true, + "serverrequired": true + }, + "self": { + "name": "self", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xid": { + "name": "xid", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "xref": { + "name": "xref", + "type": "url" + }, + "epoch": { + "name": "epoch", + "type": "uinteger", + "serverrequired": true + }, + "createdat": { + "name": "createdat", + "type": "timestamp", + "serverrequired": true + }, + "modifiedat": { + "name": "modifiedat", + "type": "timestamp", + "serverrequired": true + }, + "defaultversionid": { + "name": "defaultversionid", + "type": "string", + "serverrequired": true + }, + "defaultversionurl": { + "name": "defaultversionurl", + "type": "url", + "readonly": true, + "serverrequired": true + }, + "defaultversionsticky": { + "name": "defaultversionsticky", + "type": "boolean", + "readonly": true + } + } + } + } + } + } + }, + + "dirsurl": "http://localhost:8181/dirs", + "dirscount": 1 +} +`) + + // Play with ?export not at root + xHTTP(t, reg, "GET", "/dirs?export", ``, 200, `{ + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "xid": "/dirs/d1", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:03Z", + + "filesurl": "http://localhost:8181/dirs/d1/files", + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "meta": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:04Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure" + }, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "xid": "/dirs/d1/files/f1/versions/v1", + "epoch": 1, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:02Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + }, + "v2": { + "fileid": "f1", + "versionid": "v2", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure", + "xid": "/dirs/d1/files/f1/versions/v2", + "epoch": 1, + "isdefault": true, + "createdat": "2025-01-01T12:00:04Z", + "modifiedat": "2025-01-01T12:00:04Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + } + }, + "versionscount": 2 + }, + "fx": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx$structure", + "xid": "/dirs/d1/files/fx", + + "metaurl": "http://localhost:8181/dirs/d1/files/fx/meta", + "meta": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" + } + } + }, + "filescount": 2 + } +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1?export", ``, 200, `{ + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "xid": "/dirs/d1", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:03Z", + + "filesurl": "http://localhost:8181/dirs/d1/files", + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "meta": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:04Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure" + }, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "xid": "/dirs/d1/files/f1/versions/v1", + "epoch": 1, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:02Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + }, + "v2": { + "fileid": "f1", + "versionid": "v2", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure", + "xid": "/dirs/d1/files/f1/versions/v2", + "epoch": 1, + "isdefault": true, + "createdat": "2025-01-01T12:00:04Z", + "modifiedat": "2025-01-01T12:00:04Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + } + }, + "versionscount": 2 + }, + "fx": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx$structure", + "xid": "/dirs/d1/files/fx", + + "metaurl": "http://localhost:8181/dirs/d1/files/fx/meta", + "meta": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" + } + } + }, + "filescount": 2 +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files?export", ``, 200, `{ + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "meta": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:04Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure" + }, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "xid": "/dirs/d1/files/f1/versions/v1", + "epoch": 1, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:02Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + }, + "v2": { + "fileid": "f1", + "versionid": "v2", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure", + "xid": "/dirs/d1/files/f1/versions/v2", + "epoch": 1, + "isdefault": true, + "createdat": "2025-01-01T12:00:04Z", + "modifiedat": "2025-01-01T12:00:04Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + } + }, + "versionscount": 2 + }, + "fx": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx$structure", + "xid": "/dirs/d1/files/fx", + + "metaurl": "http://localhost:8181/dirs/d1/files/fx/meta", + "meta": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" + } + } +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/f1?export", ``, 200, `{ + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "meta": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:04Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure" + }, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versions": { + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "xid": "/dirs/d1/files/f1/versions/v1", + "epoch": 1, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:02Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + }, + "v2": { + "fileid": "f1", + "versionid": "v2", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure", + "xid": "/dirs/d1/files/f1/versions/v2", + "epoch": 1, + "isdefault": true, + "createdat": "2025-01-01T12:00:04Z", + "modifiedat": "2025-01-01T12:00:04Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + } + }, + "versionscount": 2 +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/f1/meta?export", ``, 200, `{ + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:04Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure" +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/fx?export", ``, 200, `{ + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx$structure", + "xid": "/dirs/d1/files/fx", + + "metaurl": "http://localhost:8181/dirs/d1/files/fx/meta", + "meta": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" + } +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/fx/meta?export", ``, 200, `{ + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/f1/versions?export", ``, 200, `{ + "v1": { + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "xid": "/dirs/d1/files/f1/versions/v1", + "epoch": 1, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:02Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + }, + "v2": { + "fileid": "f1", + "versionid": "v2", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure", + "xid": "/dirs/d1/files/f1/versions/v2", + "epoch": 1, + "isdefault": true, + "createdat": "2025-01-01T12:00:04Z", + "modifiedat": "2025-01-01T12:00:04Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + } +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/f1/versions/v1?export", ``, 200, `{ + "fileid": "f1", + "versionid": "v1", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v1$structure", + "xid": "/dirs/d1/files/f1/versions/v1", + "epoch": 1, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:02Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } +} +`) + + xHTTP(t, reg, "GET", "/dirs/d1/files/fx/versions?export", ``, 404, + "Not found\n") + + xHTTP(t, reg, "GET", "/dirs/d1/files/fx/versions/v1?export", ``, 404, + "Not found\n") + + // Just some filtering too for fun + code, fullBody = xGET(t, "export?filter=dirs.files.versions.versionid=v2&inline=*") + xCheckEqual(t, "", code, 200) + code, manualBody = xGET(t, "?export&filter=dirs.files.versions.versionid=v2") + xCheckEqual(t, "", code, 200) + xCheckEqual(t, "", fullBody, manualBody) + + xCheckEqual(t, "", fullBody, `{ + "specversion": "0.5", + "registryid": "TestExportRoot", + "self": "http://localhost:8181/", + "xid": "/", + "epoch": 2, + "createdat": "2025-01-01T12:00:01Z", + "modifiedat": "2025-01-01T12:00:02Z", + + "dirsurl": "http://localhost:8181/dirs", + "dirs": { + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "xid": "/dirs/d1", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:03Z", + + "filesurl": "http://localhost:8181/dirs/d1/files", + "files": { + "f1": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1$structure", + "xid": "/dirs/d1/files/f1", + + "metaurl": "http://localhost:8181/dirs/d1/files/f1/meta", + "meta": { + "fileid": "f1", + "self": "http://localhost:8181/dirs/d1/files/f1/meta", + "xid": "/dirs/d1/files/f1/meta", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:04Z", + + "defaultversionid": "v2", + "defaultversionurl": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure" + }, + "versionsurl": "http://localhost:8181/dirs/d1/files/f1/versions", + "versions": { + "v2": { + "fileid": "f1", + "versionid": "v2", + "self": "http://localhost:8181/dirs/d1/files/f1/versions/v2$structure", + "xid": "/dirs/d1/files/f1/versions/v2", + "epoch": 1, + "isdefault": true, + "createdat": "2025-01-01T12:00:04Z", + "modifiedat": "2025-01-01T12:00:04Z", + "contenttype": "application/json", + "file": { + "hello": "world" + } + } + }, + "versionscount": 1 + }, + "fx": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx$structure", + "xid": "/dirs/d1/files/fx", + + "metaurl": "http://localhost:8181/dirs/d1/files/fx/meta", + "meta": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" + } + } + }, + "filescount": 2 + } + }, + "dirscount": 1 +} +`) + + code, fullBody = xGET(t, "export?filter=dirs.files.versions.versionid=vx&inline=*") + xCheckEqual(t, "", code, 404) + code, manualBody = xGET(t, "?export&filter=dirs.files.versions.versionid=vx") + xCheckEqual(t, "", code, 404) + xCheckEqual(t, "", fullBody, manualBody) + xCheckEqual(t, "", fullBody, "Not found\n") + + code, fullBody = xGET(t, "export?filter=dirs.files.versions.versionid=v2,dirs.files.fileid=fx&inline=*") + xCheckEqual(t, "", code, 200) + code, manualBody = xGET(t, "?export&filter=dirs.files.versions.versionid=v2,dirs.files.fileid=fx") + xCheckEqual(t, "", code, 200) + xCheckEqual(t, "", fullBody, manualBody) + + xCheckEqual(t, "", fullBody, `{ + "specversion": "0.5", + "registryid": "TestExportRoot", + "self": "http://localhost:8181/", + "xid": "/", + "epoch": 2, + "createdat": "2025-01-01T12:00:01Z", + "modifiedat": "2025-01-01T12:00:02Z", + + "dirsurl": "http://localhost:8181/dirs", + "dirs": { + "d1": { + "dirid": "d1", + "self": "http://localhost:8181/dirs/d1", + "xid": "/dirs/d1", + "epoch": 2, + "createdat": "2025-01-01T12:00:02Z", + "modifiedat": "2025-01-01T12:00:03Z", + + "filesurl": "http://localhost:8181/dirs/d1/files", + "files": { + "fx": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx$structure", + "xid": "/dirs/d1/files/fx", + + "metaurl": "http://localhost:8181/dirs/d1/files/fx/meta", + "meta": { + "fileid": "fx", + "self": "http://localhost:8181/dirs/d1/files/fx/meta", + "xid": "/dirs/d1/files/fx/meta", + "xref": "/dirs/d1/files/f1" + } + } + }, + "filescount": 1 + } + }, + "dirscount": 1 +} +`) + +} diff --git a/tests/http2_test.go b/tests/http2_test.go index f92f6b30..aa613e87 100644 --- a/tests/http2_test.go +++ b/tests/http2_test.go @@ -1995,7 +1995,7 @@ func TestHTTPContent(t *testing.T) { } `) - // patch - has ct, set ct to null + // patch - has ct, set ct to null, file contents should remain xCheckHTTP(t, reg, &HTTPTest{ URL: "/dirs/d1/files/f18$structure", Method: "PATCH", diff --git a/tests/utils_test.go b/tests/utils_test.go index 02fbddb0..cd98c248 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -308,6 +308,26 @@ type HTTPTest struct { ResBody string } +// http code, body +func xGET(t *testing.T, url string) (int, string) { + t.Helper() + url = "http://localhost:8181/" + url + res, err := http.Get(url) + if err != nil { + t.Fatalf("HTTP GET error: %s", err) + } + + body, _ := io.ReadAll(res.Body) + /* + if res.StatusCode != 200 { + t.Logf("URL: %s", url) + t.Logf("Code: %d\n%s", res.StatusCode, string(body)) + } + */ + + return res.StatusCode, string(body) +} + func xHTTP(t *testing.T, reg *registry.Registry, verb, url, reqBody string, code int, resBody string) { t.Helper() xCheckHTTP(t, reg, &HTTPTest{