mirror of https://github.com/perkeep/perkeep.git
426 lines
11 KiB
Go
426 lines
11 KiB
Go
/*
|
|
Copyright 2011 Google Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package index
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"camli/blobref"
|
|
"camli/jsonsign"
|
|
"camli/schema"
|
|
"camli/search"
|
|
"camli/test"
|
|
)
|
|
|
|
var _ = log.Printf
|
|
|
|
type IndexDeps struct {
|
|
Index *Index
|
|
|
|
BlobSource *test.Fetcher
|
|
|
|
// Following three needed for signing:
|
|
PublicKeyFetcher *test.Fetcher
|
|
EntityFetcher jsonsign.EntityFetcher // fetching decrypted openpgp entities
|
|
SignerBlobRef *blobref.BlobRef
|
|
|
|
now int64 // fake clock, nanos since epoch
|
|
}
|
|
|
|
func (id *IndexDeps) Get(key string) string {
|
|
v, _ := id.Index.s.Get(key)
|
|
return v
|
|
}
|
|
|
|
func (id *IndexDeps) dumpIndex(t *testing.T) {
|
|
t.Logf("Begin index dump:")
|
|
it := id.Index.s.Find("")
|
|
for it.Next() {
|
|
t.Logf(" %q = %q", it.Key(), it.Value())
|
|
}
|
|
if err := it.Close(); err != nil {
|
|
t.Fatalf("iterator close = %v", err)
|
|
}
|
|
t.Logf("End index dump.")
|
|
}
|
|
|
|
func (id *IndexDeps) uploadAndSignMap(m map[string]interface{}) *blobref.BlobRef {
|
|
m["camliSigner"] = id.SignerBlobRef
|
|
unsigned, err := schema.MapToCamliJson(m)
|
|
if err != nil {
|
|
panic("uploadAndSignMap: " + err.String())
|
|
}
|
|
sr := &jsonsign.SignRequest{
|
|
UnsignedJson: unsigned,
|
|
Fetcher: id.PublicKeyFetcher,
|
|
EntityFetcher: id.EntityFetcher,
|
|
}
|
|
signed, err := sr.Sign()
|
|
if err != nil {
|
|
panic("problem signing: " + err.String())
|
|
}
|
|
tb := &test.Blob{Contents: signed}
|
|
_, err = id.Index.ReceiveBlob(tb.BlobRef(), tb.Reader())
|
|
if err != nil {
|
|
panic(fmt.Sprintf("problem indexing blob: %v\nblob was:\n%s", err, signed))
|
|
}
|
|
return tb.BlobRef()
|
|
}
|
|
|
|
// NewPermanode creates (& signs) a new permanode and adds it
|
|
// to the index, returning its blobref.
|
|
func (id *IndexDeps) NewPermanode() *blobref.BlobRef {
|
|
unsigned := schema.NewUnsignedPermanode()
|
|
return id.uploadAndSignMap(unsigned)
|
|
}
|
|
|
|
func (id *IndexDeps) advanceTime() string {
|
|
id.now += 1e9
|
|
return schema.RFC3339FromNanos(id.now)
|
|
}
|
|
|
|
func (id *IndexDeps) lastTimeNanos() int64 {
|
|
return id.now
|
|
}
|
|
|
|
func (id *IndexDeps) SetAttribute(permaNode *blobref.BlobRef, attr, value string) *blobref.BlobRef {
|
|
m := schema.NewSetAttributeClaim(permaNode, attr, value)
|
|
m["claimDate"] = id.advanceTime()
|
|
return id.uploadAndSignMap(m)
|
|
}
|
|
|
|
func (id *IndexDeps) UploadFile(fileName string, contents string) (fileRef, wholeRef *blobref.BlobRef) {
|
|
cb := &test.Blob{Contents: contents}
|
|
id.BlobSource.AddBlob(cb)
|
|
wholeRef = cb.BlobRef()
|
|
_, err := id.Index.ReceiveBlob(wholeRef, cb.Reader())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
m := schema.NewFileMap(fileName)
|
|
schema.PopulateParts(m, int64(len(contents)), []schema.BytesPart{
|
|
schema.BytesPart{
|
|
Size: uint64(len(contents)),
|
|
BlobRef: wholeRef,
|
|
}})
|
|
fjson, err := schema.MapToCamliJson(m)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fb := &test.Blob{Contents: fjson}
|
|
log.Printf("Blob is: %s", fjson)
|
|
id.BlobSource.AddBlob(fb)
|
|
fileRef = fb.BlobRef()
|
|
_, err = id.Index.ReceiveBlob(fileRef, fb.Reader())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func NewIndexDeps() *IndexDeps {
|
|
secretRingFile := "../../../../lib/go/camli/jsonsign/testdata/test-secring.gpg"
|
|
pubKey := &test.Blob{Contents: `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
|
|
xsBNBEzgoVsBCAC/56aEJ9BNIGV9FVP+WzenTAkg12k86YqlwJVAB/VwdMlyXxvi
|
|
bCT1RVRfnYxscs14LLfcMWF3zMucw16mLlJCBSLvbZ0jn4h+/8vK5WuAdjw2YzLs
|
|
WtBcjWn3lV6tb4RJz5gtD/o1w8VWxwAnAVIWZntKAWmkcChCRgdUeWso76+plxE5
|
|
aRYBJqdT1mctGqNEISd/WYPMgwnWXQsVi3x4z1dYu2tD9uO1dkAff12z1kyZQIBQ
|
|
rexKYRRRh9IKAayD4kgS0wdlULjBU98aeEaMz1ckuB46DX3lAYqmmTEL/Rl9cOI0
|
|
Enpn/oOOfYFa5h0AFndZd1blMvruXfdAobjVABEBAAE=
|
|
=28/7
|
|
-----END PGP PUBLIC KEY BLOCK-----`}
|
|
|
|
id := &IndexDeps{
|
|
Index: newMemoryIndex(),
|
|
BlobSource: new(test.Fetcher),
|
|
PublicKeyFetcher: new(test.Fetcher),
|
|
EntityFetcher: &jsonsign.CachingEntityFetcher{
|
|
Fetcher: &jsonsign.FileEntityFetcher{File: secretRingFile},
|
|
},
|
|
SignerBlobRef: pubKey.BlobRef(),
|
|
now: 1322443956*1e9 + 123456,
|
|
}
|
|
// Add dev-camput's test key public key, keyid 26F5ABDA,
|
|
// blobref sha1-ad87ca5c78bd0ce1195c46f7c98e6025abbaf007
|
|
if id.SignerBlobRef.String() != "sha1-ad87ca5c78bd0ce1195c46f7c98e6025abbaf007" {
|
|
panic("unexpected signer blobref")
|
|
}
|
|
id.PublicKeyFetcher.AddBlob(pubKey)
|
|
id.Index.KeyFetcher = id.PublicKeyFetcher
|
|
id.Index.BlobSource = id.BlobSource
|
|
return id
|
|
}
|
|
|
|
func TestIndex(t *testing.T) {
|
|
id := NewIndexDeps()
|
|
pn := id.NewPermanode()
|
|
t.Logf("uploaded permanode %q", pn)
|
|
br1 := id.SetAttribute(pn, "foo", "foo1")
|
|
br1Time := id.lastTimeNanos()
|
|
t.Logf("set attribute %q", br1)
|
|
br2 := id.SetAttribute(pn, "foo", "foo2")
|
|
br2Time := id.lastTimeNanos()
|
|
t.Logf("set attribute %q", br2)
|
|
rootClaim := id.SetAttribute(pn, "camliRoot", "rootval")
|
|
rootClaimTime := id.lastTimeNanos()
|
|
t.Logf("set attribute %q", rootClaim)
|
|
|
|
id.dumpIndex(t)
|
|
|
|
key := "signerkeyid:sha1-ad87ca5c78bd0ce1195c46f7c98e6025abbaf007"
|
|
if g, e := id.Get(key), "2931A67C26F5ABDA"; g != e {
|
|
t.Fatalf("%q = %q, want %q", key, g, e)
|
|
}
|
|
|
|
key = "have:" + pn.String()
|
|
pnSizeStr := id.Get(key)
|
|
if pnSizeStr == "" {
|
|
t.Fatalf("missing key %q", key)
|
|
}
|
|
|
|
key = "meta:" + pn.String()
|
|
if g, e := id.Get(key), pnSizeStr+"|application/json; camliType=permanode"; g != e {
|
|
t.Errorf("key %q = %q, want %q", key, g, e)
|
|
}
|
|
|
|
key = "recpn|2931A67C26F5ABDA|rt7988-88-71T98:67:62.999876543Z|" + br1.String()
|
|
if g, e := id.Get(key), pn.String(); g != e {
|
|
t.Fatalf("%q = %q, want %q (permanode)", key, g, e)
|
|
}
|
|
|
|
key = "recpn|2931A67C26F5ABDA|rt7988-88-71T98:67:61.999876543Z|" + br2.String()
|
|
if g, e := id.Get(key), pn.String(); g != e {
|
|
t.Fatalf("%q = %q, want %q (permanode)", key, g, e)
|
|
}
|
|
|
|
// PermanodeOfSignerAttrValue
|
|
{
|
|
gotPN, err := id.Index.PermanodeOfSignerAttrValue(id.SignerBlobRef, "camliRoot", "rootval")
|
|
if err != nil {
|
|
t.Fatalf("id.Index.PermanodeOfSignerAttrValue = %v", err)
|
|
}
|
|
if gotPN.String() != pn.String() {
|
|
t.Errorf("id.Index.PermanodeOfSignerAttrValue = %q, want %q", gotPN, pn)
|
|
}
|
|
_, err = id.Index.PermanodeOfSignerAttrValue(id.SignerBlobRef, "camliRoot", "MISSING")
|
|
if err == nil {
|
|
t.Errorf("expected an error from PermanodeOfSignerAttrValue on missing value")
|
|
}
|
|
}
|
|
|
|
// GetRecentPermanodes
|
|
{
|
|
ch := make(chan *search.Result, 10) // only expect 1 result, but 3 if buggy.
|
|
err := id.Index.GetRecentPermanodes(ch, id.SignerBlobRef, 50)
|
|
if err != nil {
|
|
t.Fatalf("GetRecentPermanodes = %v", err)
|
|
}
|
|
got := []*search.Result{}
|
|
for r := range ch {
|
|
got = append(got, r)
|
|
}
|
|
want := []*search.Result{
|
|
&search.Result{
|
|
BlobRef: pn,
|
|
Signer: id.SignerBlobRef,
|
|
LastModTime: 1322443959,
|
|
},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("GetRecentPermanode results differ.\n got: %v\nwant: %v",
|
|
search.Results(got), search.Results(want))
|
|
}
|
|
}
|
|
|
|
// GetBlobMimeType
|
|
{
|
|
mime, size, err := id.Index.GetBlobMimeType(pn)
|
|
if err != nil {
|
|
t.Errorf("GetBlobMimeType(%q) = %v", pn, err)
|
|
} else {
|
|
if e := "application/json; camliType=permanode"; mime != e {
|
|
t.Errorf("GetBlobMimeType(%q) mime = %q, want %q", pn, mime, e)
|
|
}
|
|
if size == 0 {
|
|
t.Errorf("GetBlobMimeType(%q) size is zero", pn)
|
|
}
|
|
}
|
|
_, _, err = id.Index.GetBlobMimeType(blobref.Parse("abc-123"))
|
|
if err != os.ENOENT {
|
|
t.Errorf("GetBlobMimeType(dummy blobref) = %v; want os.ENOENT", err)
|
|
}
|
|
}
|
|
|
|
// GetOwnerClaims
|
|
{
|
|
claims, err := id.Index.GetOwnerClaims(pn, id.SignerBlobRef)
|
|
if err != nil {
|
|
t.Errorf("GetOwnerClaims = %v", err)
|
|
} else {
|
|
want := search.ClaimList([]*search.Claim{
|
|
&search.Claim{
|
|
BlobRef: br1,
|
|
Permanode: pn,
|
|
Signer: id.SignerBlobRef,
|
|
Date: time.NanosecondsToUTC(br1Time),
|
|
Type: "set-attribute",
|
|
Attr: "foo",
|
|
Value: "foo1",
|
|
},
|
|
&search.Claim{
|
|
BlobRef: br2,
|
|
Permanode: pn,
|
|
Signer: id.SignerBlobRef,
|
|
Date: time.NanosecondsToUTC(br2Time),
|
|
Type: "set-attribute",
|
|
Attr: "foo",
|
|
Value: "foo2",
|
|
},
|
|
&search.Claim{
|
|
BlobRef: rootClaim,
|
|
Permanode: pn,
|
|
Signer: id.SignerBlobRef,
|
|
Date: time.NanosecondsToUTC(rootClaimTime),
|
|
Type: "set-attribute",
|
|
Attr: "camliRoot",
|
|
Value: "rootval",
|
|
},
|
|
})
|
|
if !reflect.DeepEqual(claims, want) {
|
|
t.Errorf("GetOwnerClaims results differ.\n got: %v\nwant: %v",
|
|
claims, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPathsOfSignerTarget(t *testing.T) {
|
|
id := NewIndexDeps()
|
|
pn := id.NewPermanode()
|
|
t.Logf("uploaded permanode %q", pn)
|
|
|
|
claim1 := id.SetAttribute(pn, "camliPath:somedir", "targ-123")
|
|
claim2 := id.SetAttribute(pn, "camliPath:with|pipe", "targ-124")
|
|
t.Logf("made path claims %q and %q", claim1, claim2)
|
|
|
|
id.dumpIndex(t)
|
|
|
|
type test struct {
|
|
blobref string
|
|
want int
|
|
}
|
|
tests := []test{
|
|
{"targ-123", 1},
|
|
{"targ-124", 1},
|
|
{"targ-125", 0},
|
|
}
|
|
for _, tt := range tests {
|
|
signer := id.SignerBlobRef
|
|
paths, err := id.Index.PathsOfSignerTarget(signer, blobref.Parse(tt.blobref))
|
|
if err != nil {
|
|
t.Fatalf("PathsOfSignerTarget(%q): %v", tt.blobref, err)
|
|
}
|
|
if len(paths) != tt.want {
|
|
t.Fatalf("PathsOfSignerTarget(%q) got %d results; want %d",
|
|
tt.blobref, len(paths), tt.want)
|
|
}
|
|
if tt.blobref == "targ-123" {
|
|
p := paths[0]
|
|
want := fmt.Sprintf(
|
|
"Path{Claim: %s, 2011-11-28T01:32:37.000123456Z; Base: %s + Suffix \"somedir\" => Target targ-123}",
|
|
claim1, pn)
|
|
if g := p.String(); g != want {
|
|
t.Errorf("claim wrong.\n got: %s\nwant: %s", g, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
path, err := id.Index.PathLookup(id.SignerBlobRef, pn, "with|pipe", nil)
|
|
if err != nil {
|
|
t.Fatalf("PathLookup = %v", err)
|
|
}
|
|
if g, e := path.Target.String(), "targ-124"; g != e {
|
|
t.Errorf("PathLookup = %q; want %q", g, e)
|
|
}
|
|
}
|
|
|
|
func TestFiles(t *testing.T) {
|
|
id := NewIndexDeps()
|
|
fileRef, wholeRef := id.UploadFile("foo.html", "<html>I am an html file.</html>")
|
|
t.Logf("uploaded fileref %q, wholeRef %q", fileRef, wholeRef)
|
|
id.dumpIndex(t)
|
|
|
|
// ExistingFileSchemas
|
|
{
|
|
key := fmt.Sprintf("wholetofile|%s|%s", wholeRef, fileRef)
|
|
if g, e := id.Get(key), "1"; g != e {
|
|
t.Fatalf("%q = %q, want %q", key, g, e)
|
|
}
|
|
|
|
refs, err := id.Index.ExistingFileSchemas(wholeRef)
|
|
if err != nil {
|
|
t.Fatalf("ExistingFileSchemas = %v", err)
|
|
}
|
|
want := []*blobref.BlobRef{fileRef}
|
|
if !reflect.DeepEqual(refs, want) {
|
|
t.Errorf("ExistingFileSchemas got = %#v, want %#v", refs, want)
|
|
}
|
|
}
|
|
|
|
// FileInfo
|
|
{
|
|
key := fmt.Sprintf("fileinfo|%s", fileRef)
|
|
if g, e := id.Get(key), "31|foo.html|text%2Fhtml"; g != e {
|
|
t.Fatalf("%q = %q, want %q", key, g, e)
|
|
}
|
|
|
|
fi, err := id.Index.GetFileInfo(fileRef)
|
|
if err != nil {
|
|
t.Fatalf("GetFileInfo = %v", err)
|
|
}
|
|
if g, e := fi.Size, int64(31); g != e {
|
|
t.Errorf("Size = %d, want %d", g, e)
|
|
}
|
|
if g, e := fi.FileName, "foo.html"; g != e {
|
|
t.Errorf("FileName = %q, want %q", g, e)
|
|
}
|
|
if g, e := fi.MimeType, "text/html"; g != e {
|
|
t.Errorf("MimeType = %q, want %q", g, e)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReverseTimeString(t *testing.T) {
|
|
in := "2011-11-27T01:23:45Z"
|
|
got := reverseTimeString(in)
|
|
want := "rt7988-88-72T98:76:54Z"
|
|
if got != want {
|
|
t.Fatalf("reverseTimeString = %q, want %q", got, want)
|
|
}
|
|
back := unreverseTimeString(got)
|
|
if back != in {
|
|
t.Fatalf("unreverseTimeString = %q, want %q", back, in)
|
|
}
|
|
}
|