Newer
Older
// Copyright (c) 2012-2015 The upper.io/db authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Tests for the mongodb adapter.
package mongo
import (
José Carlos Nieto
committed
"math/rand"
"strings"
"time"
"gopkg.in/mgo.v2/bson"
// Wrapper settings.
José Carlos Nieto
committed
const (
username = "upperio_tests"
password = "upperio_secret"
José Carlos Nieto
committed
)
// Global settings for tests.
var settings = ConnectionURL{
Database: database,
User: username,
Password: password,
// Structure for testing conversions and datatypes.
Uint uint `bson:"_uint"`
Uint8 uint8 `bson:"_uint8"`
Uint16 uint16 `bson:"_uint16"`
Uint32 uint32 `bson:"_uint32"`
Uint64 uint64 `bson:"_uint64"`
Int int `bson:"_int"`
Int8 int8 `bson:"_int8"`
Int16 int16 `bson:"_int16"`
Int32 int32 `bson:"_int32"`
Int64 int64 `bson:"_int64"`
Float32 float32 `bson:"_float32"`
Float64 float64 `bson:"_float64"`
Bool bool `bson:"_bool"`
String string `bson:"_string"`
José Carlos Nieto
committed
Date time.Time `bson:"_date"`
DateN *time.Time `bson:"_nildate"`
DateP *time.Time `bson:"_ptrdate"`
Time time.Duration `bson:"_time"`
José Carlos Nieto
committed
type artistWithObjectIdKey struct {
id bson.ObjectId
Name string `db:"name"`
}
func (artist *artistWithObjectIdKey) SetID(id bson.ObjectId) error {
artist.id = id
return nil
}
type itemWithKey struct {
ID bson.ObjectId `bson:"-"`
SomeVal string `bson:"some_val"`
}
José Carlos Nieto
committed
func (item itemWithKey) Constraint() db.Cond {
cond := db.Cond{
"_id": item.ID,
}
return cond
}
José Carlos Nieto
committed
func (item *itemWithKey) SetID(keys map[string]interface{}) error {
if len(keys) == 1 {
item.ID = keys["_id"].(bson.ObjectId)
return nil
}
return errors.New(`Expecting exactly two keys.`)
}
José Carlos Nieto
committed
var testValues testValuesStruct
func init() {
t := time.Date(2012, 7, 28, 1, 2, 3, 0, time.Local)
testValues = testValuesStruct{
1, 1, 1, 1, 1,
-1, -1, -1, -1, -1,
1.337, 1.337,
true,
"Hello world!",
t,
nil,
&t,
time.Second * time.Duration(7331),
}
if host = os.Getenv("TEST_HOST"); host == "" {
host = "localhost"
}
settings.Host = host
_, err := db.Open(Adapter, ConnectionURL{})
if err != nil {
t.Errorf(err.Error())
}
// Attempts to open an empty datasource.
func TestOpenWithWrongData(t *testing.T) {
var err error
var rightSettings, wrongSettings ConnectionURL
// Attempt to open with safe settings.
rightSettings = ConnectionURL{
User: username,
Password: password,
}
// Attempt to open an empty database.
if _, err = db.Open(Adapter, rightSettings); err != nil {
// Must fail.
t.Fatal(err)
}
// Attempt to open with wrong password.
wrongSettings = ConnectionURL{
User: username,
Password: "fail",
}
if _, err = db.Open(Adapter, wrongSettings); err == nil {
t.Fatalf("Expecting an error.")
}
// Attempt to open with wrong database.
wrongSettings = ConnectionURL{
User: username,
Password: password,
}
if _, err = db.Open(Adapter, wrongSettings); err == nil {
t.Fatalf("Expecting an error.")
}
// Attempt to open with wrong username.
wrongSettings = ConnectionURL{
User: "fail",
Password: password,
}
if _, err = db.Open(Adapter, wrongSettings); err == nil {
t.Fatalf("Expecting an error.")
}
}
// Truncates all collections.
// Opening database.
sess, err := db.Open(Adapter, settings)
t.Fatal(err)
// We should close the database when it's no longer in use.
// Getting a list of all collections in this database.
collections, err := sess.Collections()
if err != nil {
t.Fatal(err)
for _, name := range collections {
// Pointing the collection.
col := sess.Collection(name)
// The collection may ot may not exists.
exists := col.Exists()
if exists == true {
// Truncating the structure, if exists.
err = col.Truncate()
if err != nil {
t.Fatal(err)
// This test appends some data into the "artist" table.
func TestInsert(t *testing.T) {
var err error
var id interface{}
// Opening database.
sess, err := db.Open(Adapter, settings)
t.Fatal(err)
// We should close the database when it's no longer in use.
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
// Inserting a map.
id, err = artist.Insert(map[string]string{
"name": "Ozzie",
})
José Carlos Nieto
committed
if err != nil {
t.Fatalf("Insert(): %s", err.Error())
José Carlos Nieto
committed
}
if id == nil {
t.Fatalf("Expecting an ID.")
if _, ok := id.(bson.ObjectId); ok != true {
t.Fatalf("Expecting a bson.ObjectId.")
if id.(bson.ObjectId).Valid() != true {
t.Fatalf("Expecting a valid bson.ObjectId.")
// Inserting a struct.
id, err = artist.Insert(struct {
Name string
}{
"Flea",
})
if id == nil {
t.Fatalf("Expecting an ID.")
if _, ok := id.(bson.ObjectId); ok != true {
t.Fatalf("Expecting a bson.ObjectId.")
if id.(bson.ObjectId).Valid() != true {
t.Fatalf("Expecting a valid bson.ObjectId.")
}
// Inserting a struct (using tags to specify the field name).
id, err = artist.Insert(struct {
ArtistName string `bson:"name"`
}{
"Slash",
})
if id == nil {
t.Fatalf("Expecting an ID.")
if _, ok := id.(bson.ObjectId); ok != true {
t.Fatalf("Expecting a bson.ObjectId.")
if id.(bson.ObjectId).Valid() != true {
t.Fatalf("Expecting a valid bson.ObjectId.")
// Inserting a pointer to a struct
id, err = artist.Insert(&struct {
ArtistName string `bson:"name"`
}{
"Metallica",
})
if id == nil {
t.Fatalf("Expecting an ID.")
}
if _, ok := id.(bson.ObjectId); ok != true {
t.Fatalf("Expecting a bson.ObjectId.")
}
if id.(bson.ObjectId).Valid() != true {
t.Fatalf("Expecting a valid bson.ObjectId.")
}
// Inserting a pointer to a map
id, err = artist.Insert(&map[string]string{
"name": "Freddie",
})
if id == nil {
t.Fatalf("Expecting an ID.")
}
if _, ok := id.(bson.ObjectId); ok != true {
t.Fatalf("Expecting a bson.ObjectId.")
}
if id.(bson.ObjectId).Valid() != true {
t.Fatalf("Expecting a valid bson.ObjectId.")
}
José Carlos Nieto
committed
// Attempt to append and update a private key
itemStruct3 := artistWithObjectIdKey{
Name: "Janus",
}
if _, err = artist.Insert(&itemStruct3); err != nil {
José Carlos Nieto
committed
t.Fatal(err)
}
if itemStruct3.id.Valid() == false {
t.Fatalf("Expecting an ID.")
}
var total uint64
José Carlos Nieto
committed
if total, err = artist.Find().Count(); err != nil {
t.Fatal(err)
}
if total != 6 {
t.Fatalf("Expecting exactly 6 rows.")
José Carlos Nieto
committed
}
// This test tries to use an empty filter and count how many elements were
// added into the artist collection.
func TestResultCount(t *testing.T) {
var res db.Result
// Opening database.
sess, err := db.Open(Adapter, settings)
t.Fatal(err)
// We should close the database when it's no longer in use.
artist := sess.Collection("artist")
res = artist.Find()
// Counting all the matching rows.
total, err := res.Count()
t.Fatal(err)
if total == 0 {
t.Fatalf("Should not be empty, we've just added some rows!")
José Carlos Nieto
committed
func TestGroup(t *testing.T) {
var err error
var sess db.Database
var stats db.Collection
if sess, err = db.Open(Adapter, settings); err != nil {
t.Fatal(err)
}
José Carlos Nieto
committed
Numeric int `db:"numeric" bson:"numeric"`
Value int `db:"value" bson:"value"`
}
defer sess.Close()
stats = sess.Collection("statsTest")
José Carlos Nieto
committed
// Truncating table.
José Carlos Nieto
committed
// Adding row append.
for i := 0; i < 1000; i++ {
numeric, value := rand.Intn(10), rand.Intn(100)
if _, err = stats.Insert(statsT{numeric, value}); err != nil {
José Carlos Nieto
committed
t.Fatal(err)
}
}
// db.statsTest.group({key: {numeric: true}, initial: {sum: 0}, reduce: function(doc, prev) { prev.sum += 1}});
José Carlos Nieto
committed
// Testing GROUP BY
res := stats.Find().Group(bson.M{
"key": bson.M{"numeric": true},
"initial": bson.M{"sum": 0},
"reduce": `function(doc, prev) { prev.sum += 1}`,
})
var results []map[string]interface{}
err = res.All(&results)
// Currently not supported.
if err != db.ErrUnsupported {
t.Fatal(err)
}
//if len(results) != 10 {
// t.Fatalf(`Expecting exactly 10 results, this could fail, but it's very unlikely to happen.`)
//}
}
// Attempts to count all rows in a table that does not exist.
func TestResultNonExistentCount(t *testing.T) {
sess, err := db.Open(Adapter, settings)
if err != nil {
t.Fatal(err)
}
defer sess.Close()
total, err := sess.Collection("notartist").Find().Count()
if err != nil {
t.Fatal("MongoDB should not care about a non-existent collecton.", err)
}
if total != 0 {
t.Fatal("Counter should be zero")
}
}
// This test uses and result and tries to fetch items one by one.
func TestResultFetch(t *testing.T) {
var err error
var res db.Result
// Opening database.
sess, err := db.Open(Adapter, settings)
if err != nil {
t.Fatal(err)
}
// We should close the database when it's no longer in use.
defer sess.Close()
artist := sess.Collection("artist")
// Testing map
res = artist.Find()
if err == db.ErrNoMoreRows {
break
}
if err == nil {
t.Fatalf("Expecting an ID.")
}
if _, ok := rowM["_id"].(bson.ObjectId); ok != true {
t.Fatalf("Expecting a bson.ObjectId.")
}
if rowM["_id"].(bson.ObjectId).Valid() != true {
t.Fatalf("Expecting a valid bson.ObjectId.")
}
if name, ok := rowM["name"].(string); !ok || name == "" {
t.Fatalf("Expecting a name.")
}
} else {
t.Fatal(err)
res.Close()
// Testing struct
rowS := struct {
ID bson.ObjectId `bson:"_id"`
Name string `bson:"name"`
}{}
res = artist.Find()
if err == db.ErrNoMoreRows {
break
}
if err == nil {
t.Fatalf("Expecting a not null ID.")
}
t.Fatalf("Expecting a name.")
}
} else {
t.Fatal(err)
res.Close()
// Testing tagged struct
Value1 bson.ObjectId `bson:"_id"`
Value2 string `bson:"name"`
}{}
res = artist.Find()
if err == db.ErrNoMoreRows {
break
}
if err == nil {
t.Fatalf("Expecting a not null ID.")
}
t.Fatalf("Expecting a name.")
}
} else {
t.Fatal(err)
res.Close()
// Testing Result.All() with a slice of maps.
res = artist.Find()
allRowsM := []map[string]interface{}{}
err = res.All(&allRowsM)
t.Fatal(err)
for _, singleRowM := range allRowsM {
if singleRowM["_id"] == nil {
t.Fatalf("Expecting a not null ID.")
}
}
// Testing Result.All() with a slice of structs.
res = artist.Find()
allRowsS := []struct {
ID bson.ObjectId `bson:"_id"`
Name string
}{}
t.Fatal(err)
for _, singleRowS := range allRowsS {
if singleRowS.ID.Valid() == false {
t.Fatalf("Expecting a not null ID.")
}
// Testing Result.All() with a slice of tagged structs.
res = artist.Find()
Value1 bson.ObjectId `bson:"_id"`
Value2 string `bson:"name"`
}{}
if err != nil {
t.Fatal(err)
for _, singleRowT := range allRowsT {
if singleRowT.Value1.Valid() == false {
t.Fatalf("Expecting a not null ID.")
}
// This test tries to update some previously added rows.
func TestUpdate(t *testing.T) {
// Opening database.
sess, err := db.Open(Adapter, settings)
t.Fatal(err)
// We should close the database when it's no longer in use.
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
// Value
value := struct {
Name string
}{}
// Getting the first artist.
res := artist.Find(db.Cond{"_id $ne": nil}).Limit(1)
err = res.One(&value)
if err != nil {
t.Fatal(err)
// Updating with a map
"name": strings.ToUpper(value.Name),
}
if err != nil {
t.Fatal(err)
}
err = res.One(&value)
if err != nil {
t.Fatal(err)
José Carlos Nieto
committed
t.Fatalf("Expecting a modification.")
José Carlos Nieto
committed
}
// Updating with a struct
Name string
}{strings.ToLower(value.Name)}
José Carlos Nieto
committed
if err != nil {
t.Fatal(err)
err = res.One(&value)
t.Fatal(err)
t.Fatalf("Expecting a modification.")
}
// Updating with a tagged struct
Value1 string `bson:"name"`
}{strings.Replace(value.Name, "z", "Z", -1)}
if err != nil {
t.Fatal(err)
err = res.One(&value)
if err != nil {
t.Fatal(err)
t.Fatalf("Expecting a modification.")
func TestOperators(t *testing.T) {
var err error
var res db.Result
// Opening database.
sess, err := db.Open(Adapter, settings)
t.Fatal(err)
}
// We should close the database when it's no longer in use.
defer sess.Close()
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
res = artist.Find(db.Cond{"_id NOT IN": []int{0, -1}})
t.Fatalf("One: %q", err)
}
res.Close()
}
// This test tries to remove some previously added rows.
func TestRemove(t *testing.T) {
var err error
// Opening database.
sess, err := db.Open(Adapter, settings)
t.Fatal(err)
// We should close the database when it's no longer in use.
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
José Carlos Nieto
committed
// Getting the first artist.
res := artist.Find(db.Cond{"_id $ne": nil}).Limit(1)
var first struct {
err = res.One(&first)
t.Fatal(err)
}
res = artist.Find(db.Cond{"_id": first.ID})
// Trying to remove the row.
err = res.Remove()
t.Fatal(err)
// MongoDB: Does not support schemas so it can't has composite keys. We're
// testing db.Constrainer and db.IDSetter interface.
func TestSetterAndConstrainer(t *testing.T) {
var err error
var id interface{}
var sess db.Database
var compositeKeys db.Collection
if sess, err = db.Open(Adapter, settings); err != nil {
t.Fatal(err)
}
defer sess.Close()
compositeKeys = sess.Collection("composite_keys")
//n := rand.Intn(100000)
José Carlos Nieto
committed
item := itemWithKey{
// "ABCDEF",
// strconv.Itoa(n),
SomeVal: "Some value",
}
if id, err = compositeKeys.Insert(&item); err != nil {
t.Fatal(err)
}
// ids := id.([]interface{})
// if ids[0].(string) != item.Code {
// t.Fatal(`Keys must match.`)
// }
//
// if ids[1].(string) != item.UserID {
// t.Fatal(`Keys must match.`)
// }
// Using constraint interface.
José Carlos Nieto
committed
res := compositeKeys.Find(itemWithKey{ID: id.(bson.ObjectId)})
José Carlos Nieto
committed
var item2 itemWithKey
if item2.SomeVal == item.SomeVal {
t.Fatal(`Values must be different before query.`)
}
if err := res.One(&item2); err != nil {
t.Fatal(err)
}
if item2.SomeVal != item.SomeVal {
t.Fatal(`Values must be equal after query.`)
}
}
// This test tries to add many different datatypes to a single row in a
// collection, then it tries to get the stored datatypes and check if the
// stored and the original values match.
// Opening database.
sess, err := db.Open(Adapter, settings)
if err != nil {
t.Fatal(err)
}
// We should close the database when it's no longer in use.
// Getting a pointer to the "data_types" collection.
dataTypes := sess.Collection("data_types")
// Inserting our test subject.
id, err := dataTypes.Insert(testValues)
t.Fatal(err)
// Trying to get the same subject we added.
res = dataTypes.Find(db.Cond{"_id": id})
exists, err := res.Count()
if err != nil {
t.Fatal(err)
if exists == 0 {
t.Errorf("Expecting an item.")
}
// Trying to dump the subject into an empty structure of the same type.
var item testValuesStruct
res.One(&item)
// The original value and the test subject must match.
if reflect.DeepEqual(item, testValues) == false {
t.Errorf("Struct is different.")
}