CI / test (push) Successful in 3s
Expose GET /v1/features/public (optional kind filter) and update Leaflet/MapLibre demos to render all public 3D assets globally, while still merging owner collections after login. Made-with: Cursor
552 lines
20 KiB
Go
552 lines
20 KiB
Go
package httpapi_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"momswap/backend/internal/app"
|
|
httpapi "momswap/backend/internal/http"
|
|
"momswap/backend/internal/store"
|
|
)
|
|
|
|
func newTestServer(adminPublicKey string) *httptest.Server {
|
|
memory := store.NewMemoryStore()
|
|
svc := app.NewService(memory, app.Config{
|
|
ChallengeTTL: 5 * time.Minute,
|
|
SessionTTL: 24 * time.Hour,
|
|
}, adminPublicKey)
|
|
svc.BootstrapAdmin(adminPublicKey)
|
|
svc.ConfigureAssetStorage(fakeSigner{})
|
|
api := httpapi.NewAPI(svc)
|
|
return httptest.NewServer(api.Routes())
|
|
}
|
|
|
|
type fakeSigner struct{}
|
|
|
|
func (fakeSigner) SignedGetObjectURL(_ context.Context, objectKey string, _ time.Duration) (string, error) {
|
|
return "http://files.local/download/" + objectKey, nil
|
|
}
|
|
|
|
func (fakeSigner) PutObject(_ context.Context, _ string, _ string, _ io.Reader, _ int64) error {
|
|
return nil
|
|
}
|
|
|
|
func (fakeSigner) GetObject(_ context.Context, objectKey string) (io.ReadCloser, string, int64, error) {
|
|
payload := []byte("fake-download:" + objectKey)
|
|
return io.NopCloser(bytes.NewReader(payload)), "application/octet-stream", int64(len(payload)), nil
|
|
}
|
|
|
|
func mustJSON(t *testing.T, value interface{}) []byte {
|
|
t.Helper()
|
|
b, err := json.Marshal(value)
|
|
if err != nil {
|
|
t.Fatalf("marshal json: %v", err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
func getJSON(t *testing.T, client *http.Client, url string, token string) (*http.Response, map[string]interface{}) {
|
|
t.Helper()
|
|
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("do request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
out := map[string]interface{}{}
|
|
_ = json.NewDecoder(resp.Body).Decode(&out)
|
|
return resp, out
|
|
}
|
|
|
|
func postJSON(t *testing.T, client *http.Client, url string, body interface{}, token string) (*http.Response, map[string]interface{}) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(mustJSON(t, body)))
|
|
if err != nil {
|
|
t.Fatalf("new request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("do request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
out := map[string]interface{}{}
|
|
_ = json.NewDecoder(resp.Body).Decode(&out)
|
|
return resp, out
|
|
}
|
|
|
|
func patchJSON(t *testing.T, client *http.Client, url string, body interface{}, token string) (*http.Response, map[string]interface{}) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodPatch, url, bytes.NewReader(mustJSON(t, body)))
|
|
if err != nil {
|
|
t.Fatalf("new request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("do request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
out := map[string]interface{}{}
|
|
_ = json.NewDecoder(resp.Body).Decode(&out)
|
|
return resp, out
|
|
}
|
|
|
|
func putRaw(t *testing.T, client *http.Client, url string, payload []byte, contentType string, token string) *http.Response {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(payload))
|
|
if err != nil {
|
|
t.Fatalf("new request: %v", err)
|
|
}
|
|
if contentType != "" {
|
|
req.Header.Set("Content-Type", contentType)
|
|
}
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("do request: %v", err)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func loginUser(t *testing.T, client *http.Client, baseURL, pubB64 string, priv ed25519.PrivateKey) string {
|
|
t.Helper()
|
|
chResp, chData := postJSON(t, client, baseURL+"/v1/auth/challenge", map[string]string{"publicKey": pubB64}, "")
|
|
if chResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("create challenge status=%d body=%v", chResp.StatusCode, chData)
|
|
}
|
|
nonce := chData["nonce"].(string)
|
|
sig := ed25519.Sign(priv, []byte("login:"+nonce))
|
|
loginResp, loginData := postJSON(t, client, baseURL+"/v1/auth/login", map[string]string{
|
|
"publicKey": pubB64,
|
|
"nonce": nonce,
|
|
"signature": base64.RawURLEncoding.EncodeToString(sig),
|
|
}, "")
|
|
if loginResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("login status=%d body=%v", loginResp.StatusCode, loginData)
|
|
}
|
|
return loginData["accessToken"].(string)
|
|
}
|
|
|
|
func registerUserViaAdmin(t *testing.T, client *http.Client, baseURL, adminPub string, adminPriv ed25519.PrivateKey, adminToken string, userPub string, userPriv ed25519.PrivateKey, jti string) {
|
|
t.Helper()
|
|
payload := app.InvitationPayload{
|
|
JTI: jti,
|
|
InviterPublicKey: adminPub,
|
|
ExpiresAtUnix: time.Now().Add(time.Hour).Unix(),
|
|
MaxUses: 1,
|
|
}
|
|
payloadRaw := mustJSON(t, payload)
|
|
payloadB64 := base64.RawURLEncoding.EncodeToString(payloadRaw)
|
|
inviteSig := base64.RawURLEncoding.EncodeToString(ed25519.Sign(adminPriv, []byte("invite:"+payloadB64)))
|
|
|
|
inviteResp, inviteData := postJSON(t, client, baseURL+"/v1/invitations", map[string]string{
|
|
"invitePayloadB64": payloadB64,
|
|
"inviteSignature": inviteSig,
|
|
}, adminToken)
|
|
if inviteResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("create invitation status=%d body=%v", inviteResp.StatusCode, inviteData)
|
|
}
|
|
|
|
proofSig := base64.RawURLEncoding.EncodeToString(ed25519.Sign(userPriv, []byte("register:"+userPub+":"+jti)))
|
|
registerResp, registerData := postJSON(t, client, baseURL+"/v1/auth/register", map[string]string{
|
|
"publicKey": userPub,
|
|
"invitePayloadB64": payloadB64,
|
|
"inviteSignature": inviteSig,
|
|
"proofSignature": proofSig,
|
|
}, "")
|
|
if registerResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("register status=%d body=%v", registerResp.StatusCode, registerData)
|
|
}
|
|
}
|
|
|
|
func TestRegisterBySignature(t *testing.T) {
|
|
adminPub, _, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate admin key: %v", err)
|
|
}
|
|
adminPubB64 := base64.RawURLEncoding.EncodeToString(adminPub)
|
|
|
|
server := newTestServer(adminPubB64)
|
|
defer server.Close()
|
|
client := server.Client()
|
|
|
|
svcKeyResp, svcKeyData := getJSON(t, client, server.URL+"/v1/service-key", "")
|
|
if svcKeyResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("get service key status=%d body=%v", svcKeyResp.StatusCode, svcKeyData)
|
|
}
|
|
if svcKeyData["publicKey"] != adminPubB64 {
|
|
t.Fatalf("service key mismatch: got %v", svcKeyData["publicKey"])
|
|
}
|
|
|
|
userPub, userPriv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate user key: %v", err)
|
|
}
|
|
userPubB64 := base64.RawURLEncoding.EncodeToString(userPub)
|
|
sig := base64.RawURLEncoding.EncodeToString(ed25519.Sign(userPriv, []byte(adminPubB64)))
|
|
|
|
regResp, regData := postJSON(t, client, server.URL+"/v1/auth/register-by-signature", map[string]string{
|
|
"publicKey": userPubB64,
|
|
"signature": sig,
|
|
}, "")
|
|
if regResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("register-by-signature status=%d body=%v", regResp.StatusCode, regData)
|
|
}
|
|
|
|
userToken := loginUser(t, client, server.URL, userPubB64, userPriv)
|
|
if userToken == "" {
|
|
t.Fatal("login after register-by-signature failed")
|
|
}
|
|
}
|
|
|
|
func TestRegisterLoginAndProfile(t *testing.T) {
|
|
adminPub, adminPriv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate admin key: %v", err)
|
|
}
|
|
adminPubB64 := base64.RawURLEncoding.EncodeToString(adminPub)
|
|
|
|
server := newTestServer(adminPubB64)
|
|
defer server.Close()
|
|
client := server.Client()
|
|
|
|
adminToken := loginUser(t, client, server.URL, adminPubB64, adminPriv)
|
|
|
|
userPub, userPriv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate user key: %v", err)
|
|
}
|
|
userPubB64 := base64.RawURLEncoding.EncodeToString(userPub)
|
|
registerUserViaAdmin(t, client, server.URL, adminPubB64, adminPriv, adminToken, userPubB64, userPriv, "invite-1")
|
|
|
|
userToken := loginUser(t, client, server.URL, userPubB64, userPriv)
|
|
req, _ := http.NewRequest(http.MethodGet, server.URL+"/v1/me/keys", nil)
|
|
req.Header.Set("Authorization", "Bearer "+userToken)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("me request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("me status=%d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestCollectionOwnershipIsolation(t *testing.T) {
|
|
adminPub, adminPriv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate admin key: %v", err)
|
|
}
|
|
adminPubB64 := base64.RawURLEncoding.EncodeToString(adminPub)
|
|
|
|
server := newTestServer(adminPubB64)
|
|
defer server.Close()
|
|
client := server.Client()
|
|
|
|
adminToken := loginUser(t, client, server.URL, adminPubB64, adminPriv)
|
|
|
|
user1Pub, user1Priv, _ := ed25519.GenerateKey(rand.Reader)
|
|
user1PubB64 := base64.RawURLEncoding.EncodeToString(user1Pub)
|
|
registerUserViaAdmin(t, client, server.URL, adminPubB64, adminPriv, adminToken, user1PubB64, user1Priv, "invite-u1")
|
|
user1Token := loginUser(t, client, server.URL, user1PubB64, user1Priv)
|
|
|
|
user2Pub, user2Priv, _ := ed25519.GenerateKey(rand.Reader)
|
|
user2PubB64 := base64.RawURLEncoding.EncodeToString(user2Pub)
|
|
registerUserViaAdmin(t, client, server.URL, adminPubB64, adminPriv, adminToken, user2PubB64, user2Priv, "invite-u2")
|
|
user2Token := loginUser(t, client, server.URL, user2PubB64, user2Priv)
|
|
|
|
createCollectionResp, createCollectionData := postJSON(t, client, server.URL+"/v1/collections", map[string]string{
|
|
"name": "my places",
|
|
}, user1Token)
|
|
if createCollectionResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("create collection status=%d body=%v", createCollectionResp.StatusCode, createCollectionData)
|
|
}
|
|
collectionID := createCollectionData["id"].(string)
|
|
|
|
createFeatureResp, createFeatureData := postJSON(t, client, server.URL+"/v1/collections/"+collectionID+"/features", map[string]interface{}{
|
|
"geometry": map[string]interface{}{
|
|
"type": "Point",
|
|
"coordinates": []float64{-16.6291, 28.4636},
|
|
},
|
|
"properties": map[string]interface{}{
|
|
"name": "Santa Cruz",
|
|
},
|
|
}, user1Token)
|
|
if createFeatureResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("create feature status=%d body=%v", createFeatureResp.StatusCode, createFeatureData)
|
|
}
|
|
|
|
req, _ := http.NewRequest(http.MethodGet, server.URL+"/v1/collections/"+collectionID+"/features", nil)
|
|
req.Header.Set("Authorization", "Bearer "+user2Token)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("list features as user2: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("expected 403, got %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestAssetLifecycleAndVisibility(t *testing.T) {
|
|
adminPub, adminPriv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate admin key: %v", err)
|
|
}
|
|
adminPubB64 := base64.RawURLEncoding.EncodeToString(adminPub)
|
|
server := newTestServer(adminPubB64)
|
|
defer server.Close()
|
|
client := server.Client()
|
|
|
|
adminToken := loginUser(t, client, server.URL, adminPubB64, adminPriv)
|
|
|
|
user1Pub, user1Priv, _ := ed25519.GenerateKey(rand.Reader)
|
|
user1PubB64 := base64.RawURLEncoding.EncodeToString(user1Pub)
|
|
registerUserViaAdmin(t, client, server.URL, adminPubB64, adminPriv, adminToken, user1PubB64, user1Priv, "invite-asset-u1")
|
|
user1Token := loginUser(t, client, server.URL, user1PubB64, user1Priv)
|
|
|
|
user2Pub, user2Priv, _ := ed25519.GenerateKey(rand.Reader)
|
|
user2PubB64 := base64.RawURLEncoding.EncodeToString(user2Pub)
|
|
registerUserViaAdmin(t, client, server.URL, adminPubB64, adminPriv, adminToken, user2PubB64, user2Priv, "invite-asset-u2")
|
|
|
|
createCollectionResp, createCollectionData := postJSON(t, client, server.URL+"/v1/collections", map[string]string{
|
|
"name": "assets",
|
|
}, user1Token)
|
|
if createCollectionResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("create collection status=%d body=%v", createCollectionResp.StatusCode, createCollectionData)
|
|
}
|
|
collectionID := createCollectionData["id"].(string)
|
|
|
|
createFeatureResp, createFeatureData := postJSON(t, client, server.URL+"/v1/collections/"+collectionID+"/features", map[string]interface{}{
|
|
"geometry": map[string]interface{}{
|
|
"type": "Point",
|
|
"coordinates": []float64{-16.6291, 28.4636, 22},
|
|
},
|
|
"properties": map[string]interface{}{
|
|
"name": "feature-a",
|
|
},
|
|
}, user1Token)
|
|
if createFeatureResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("create feature status=%d body=%v", createFeatureResp.StatusCode, createFeatureData)
|
|
}
|
|
featureID := createFeatureData["id"].(string)
|
|
|
|
createAssetResp, createAssetData := postJSON(t, client, server.URL+"/v1/assets", map[string]interface{}{
|
|
"featureId": featureID,
|
|
"checksum": "ABCDEF1234",
|
|
"ext": "glb",
|
|
"kind": "3d",
|
|
"mimeType": "model/gltf-binary",
|
|
"sizeBytes": 100,
|
|
"name": "Tree",
|
|
"description": "Public tree",
|
|
"isPublic": true,
|
|
}, user1Token)
|
|
if createAssetResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("create asset status=%d body=%v", createAssetResp.StatusCode, createAssetData)
|
|
}
|
|
asset := createAssetData["asset"].(map[string]interface{})
|
|
assetID := asset["id"].(string)
|
|
|
|
createAssetResp2, createAssetData2 := postJSON(t, client, server.URL+"/v1/assets", map[string]interface{}{
|
|
"featureId": featureID,
|
|
"checksum": "abcdef1234",
|
|
"ext": "glb",
|
|
"kind": "3d",
|
|
"name": "Tree v2",
|
|
}, user1Token)
|
|
if createAssetResp2.StatusCode != http.StatusOK {
|
|
t.Fatalf("dedup create asset status=%d body=%v", createAssetResp2.StatusCode, createAssetData2)
|
|
}
|
|
asset2 := createAssetData2["asset"].(map[string]interface{})
|
|
if asset2["id"].(string) != assetID {
|
|
t.Fatalf("expected dedup asset id=%s got=%s", assetID, asset2["id"].(string))
|
|
}
|
|
|
|
uploadResp, uploadData := postJSON(t, client, server.URL+"/v1/assets/"+assetID+"/signed-upload", map[string]interface{}{
|
|
"contentType": "model/gltf-binary",
|
|
}, user1Token)
|
|
if uploadResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("signed upload status=%d body=%v", uploadResp.StatusCode, uploadData)
|
|
}
|
|
if uploadData["url"] != "/v1/assets/"+assetID+"/upload" {
|
|
t.Fatalf("unexpected signed-upload backend url: %v", uploadData["url"])
|
|
}
|
|
|
|
putResp := putRaw(t, client, server.URL+"/v1/assets/"+assetID+"/upload", []byte("glb-bytes"), "model/gltf-binary", user1Token)
|
|
defer putResp.Body.Close()
|
|
if putResp.StatusCode != http.StatusNoContent {
|
|
t.Fatalf("upload proxy status=%d", putResp.StatusCode)
|
|
}
|
|
|
|
featuresResp, featuresData := getJSON(t, client, server.URL+"/v1/collections/"+collectionID+"/features", user1Token)
|
|
if featuresResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("list features status=%d body=%v", featuresResp.StatusCode, featuresData)
|
|
}
|
|
features := featuresData["features"].([]interface{})
|
|
firstFeature := features[0].(map[string]interface{})
|
|
properties := firstFeature["properties"].(map[string]interface{})
|
|
assets := properties["assets"].([]interface{})
|
|
if len(assets) != 1 {
|
|
t.Fatalf("expected 1 linked asset, got %d", len(assets))
|
|
}
|
|
assetView := assets[0].(map[string]interface{})
|
|
if assetView["link"] != "/v1/assets/"+assetID+"/download" {
|
|
t.Fatalf("unexpected asset link: %v", assetView["link"])
|
|
}
|
|
|
|
reqDownloadPublic, _ := http.NewRequest(http.MethodGet, server.URL+"/v1/assets/"+assetID+"/download", nil)
|
|
downloadPublicResp, err := client.Do(reqDownloadPublic)
|
|
if err != nil {
|
|
t.Fatalf("download public request failed: %v", err)
|
|
}
|
|
defer downloadPublicResp.Body.Close()
|
|
if downloadPublicResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("expected public asset stream status, got %d", downloadPublicResp.StatusCode)
|
|
}
|
|
body, err := io.ReadAll(downloadPublicResp.Body)
|
|
if err != nil {
|
|
t.Fatalf("read public download body: %v", err)
|
|
}
|
|
expectedBody := fmt.Sprintf("fake-download:%s/%s.%s", user1PubB64, "abcdef1234", "glb")
|
|
if string(body) != expectedBody {
|
|
t.Fatalf("unexpected download body: %q", string(body))
|
|
}
|
|
|
|
patchResp, patchData := patchJSON(t, client, server.URL+"/v1/assets/"+assetID, map[string]interface{}{
|
|
"isPublic": false,
|
|
}, user1Token)
|
|
if patchResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("patch asset status=%d body=%v", patchResp.StatusCode, patchData)
|
|
}
|
|
|
|
reqDownloadPrivate, _ := http.NewRequest(http.MethodGet, server.URL+"/v1/assets/"+assetID+"/download", nil)
|
|
downloadPrivateResp, err := client.Do(reqDownloadPrivate)
|
|
if err != nil {
|
|
t.Fatalf("download private request failed: %v", err)
|
|
}
|
|
if downloadPrivateResp.StatusCode != http.StatusForbidden {
|
|
t.Fatalf("expected 403 for private asset, got %d", downloadPrivateResp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestListPublicFeatures(t *testing.T) {
|
|
adminPub, adminPriv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("generate admin key: %v", err)
|
|
}
|
|
adminPubB64 := base64.RawURLEncoding.EncodeToString(adminPub)
|
|
server := newTestServer(adminPubB64)
|
|
defer server.Close()
|
|
client := server.Client()
|
|
|
|
adminToken := loginUser(t, client, server.URL, adminPubB64, adminPriv)
|
|
|
|
user1Pub, user1Priv, _ := ed25519.GenerateKey(rand.Reader)
|
|
user1PubB64 := base64.RawURLEncoding.EncodeToString(user1Pub)
|
|
registerUserViaAdmin(t, client, server.URL, adminPubB64, adminPriv, adminToken, user1PubB64, user1Priv, "invite-public-u1")
|
|
user1Token := loginUser(t, client, server.URL, user1PubB64, user1Priv)
|
|
|
|
user2Pub, user2Priv, _ := ed25519.GenerateKey(rand.Reader)
|
|
user2PubB64 := base64.RawURLEncoding.EncodeToString(user2Pub)
|
|
registerUserViaAdmin(t, client, server.URL, adminPubB64, adminPriv, adminToken, user2PubB64, user2Priv, "invite-public-u2")
|
|
user2Token := loginUser(t, client, server.URL, user2PubB64, user2Priv)
|
|
|
|
c1Resp, c1Data := postJSON(t, client, server.URL+"/v1/collections", map[string]string{"name": "u1-public"}, user1Token)
|
|
if c1Resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("u1 create collection status=%d body=%v", c1Resp.StatusCode, c1Data)
|
|
}
|
|
c1ID := c1Data["id"].(string)
|
|
f1Resp, f1Data := postJSON(t, client, server.URL+"/v1/collections/"+c1ID+"/features", map[string]interface{}{
|
|
"geometry": map[string]interface{}{"type": "Point", "coordinates": []float64{-16.25, 28.46, 5}},
|
|
"properties": map[string]interface{}{
|
|
"name": "u1-public-feature",
|
|
},
|
|
}, user1Token)
|
|
if f1Resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("u1 create feature status=%d body=%v", f1Resp.StatusCode, f1Data)
|
|
}
|
|
f1ID := f1Data["id"].(string)
|
|
a1Resp, a1Data := postJSON(t, client, server.URL+"/v1/assets", map[string]interface{}{
|
|
"featureId": f1ID,
|
|
"checksum": "pub3d111",
|
|
"ext": "glb",
|
|
"kind": "3d",
|
|
"isPublic": true,
|
|
}, user1Token)
|
|
if a1Resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("u1 create public asset status=%d body=%v", a1Resp.StatusCode, a1Data)
|
|
}
|
|
a1ID := a1Data["asset"].(map[string]interface{})["id"].(string)
|
|
|
|
c2Resp, c2Data := postJSON(t, client, server.URL+"/v1/collections", map[string]string{"name": "u2-private"}, user2Token)
|
|
if c2Resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("u2 create collection status=%d body=%v", c2Resp.StatusCode, c2Data)
|
|
}
|
|
c2ID := c2Data["id"].(string)
|
|
f2Resp, f2Data := postJSON(t, client, server.URL+"/v1/collections/"+c2ID+"/features", map[string]interface{}{
|
|
"geometry": map[string]interface{}{"type": "Point", "coordinates": []float64{-16.3, 28.47, 7}},
|
|
"properties": map[string]interface{}{
|
|
"name": "u2-private-feature",
|
|
},
|
|
}, user2Token)
|
|
if f2Resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("u2 create feature status=%d body=%v", f2Resp.StatusCode, f2Data)
|
|
}
|
|
f2ID := f2Data["id"].(string)
|
|
a2Resp, a2Data := postJSON(t, client, server.URL+"/v1/assets", map[string]interface{}{
|
|
"featureId": f2ID,
|
|
"checksum": "priv3d222",
|
|
"ext": "glb",
|
|
"kind": "3d",
|
|
"isPublic": false,
|
|
}, user2Token)
|
|
if a2Resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("u2 create private asset status=%d body=%v", a2Resp.StatusCode, a2Data)
|
|
}
|
|
|
|
publicResp, publicData := getJSON(t, client, server.URL+"/v1/features/public?kind=3d", "")
|
|
if publicResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("list public features status=%d body=%v", publicResp.StatusCode, publicData)
|
|
}
|
|
publicFeatures := publicData["features"].([]interface{})
|
|
if len(publicFeatures) != 1 {
|
|
t.Fatalf("expected 1 public feature, got %d", len(publicFeatures))
|
|
}
|
|
publicFeature := publicFeatures[0].(map[string]interface{})
|
|
if publicFeature["id"].(string) != f1ID {
|
|
t.Fatalf("expected public feature id=%s got=%v", f1ID, publicFeature["id"])
|
|
}
|
|
properties := publicFeature["properties"].(map[string]interface{})
|
|
assets := properties["assets"].([]interface{})
|
|
if len(assets) != 1 {
|
|
t.Fatalf("expected 1 public asset, got %d", len(assets))
|
|
}
|
|
publicAsset := assets[0].(map[string]interface{})
|
|
if publicAsset["id"].(string) != a1ID {
|
|
t.Fatalf("expected public asset id=%s got=%v", a1ID, publicAsset["id"])
|
|
}
|
|
}
|