golib/gdb/orm/orm_test.go
2023-08-20 13:51:00 +08:00

589 lines
15 KiB
Go

//
// orm_test.go
// Copyright (C) 2023 tiglog <me@tiglog.com>
//
// Distributed under terms of the MIT license.
//
package orm_test
import (
"database/sql"
"testing"
"git.hexq.cn/tiglog/golib/gdb/orm"
"github.com/stretchr/testify/assert"
)
type AuthorEmail struct {
ID int64
Email string
}
func (a AuthorEmail) ConfigureEntity(e *orm.EntityConfigurator) {
e.
Table("emails").
Connection("default").
BelongsTo(&Post{}, orm.BelongsToConfig{})
}
type HeaderPicture struct {
ID int64
PostID int64
Link string
}
func (h HeaderPicture) ConfigureEntity(e *orm.EntityConfigurator) {
e.Table("header_pictures").BelongsTo(&Post{}, orm.BelongsToConfig{})
}
type Post struct {
ID int64
BodyText string
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
DeletedAt sql.NullTime
}
func (p Post) ConfigureEntity(e *orm.EntityConfigurator) {
e.Field("BodyText").ColumnName("body")
e.Field("ID").ColumnName("id")
e.
Table("posts").
HasMany(Comment{}, orm.HasManyConfig{}).
HasOne(HeaderPicture{}, orm.HasOneConfig{}).
HasOne(AuthorEmail{}, orm.HasOneConfig{}).
BelongsToMany(Category{}, orm.BelongsToManyConfig{IntermediateTable: "post_categories"})
}
func (p *Post) Categories() ([]Category, error) {
return orm.BelongsToMany[Category](p).All()
}
func (p *Post) Comments() *orm.QueryBuilder[Comment] {
return orm.HasMany[Comment](p)
}
type Comment struct {
ID int64
PostID int64
Body string
}
func (c Comment) ConfigureEntity(e *orm.EntityConfigurator) {
e.Table("comments").BelongsTo(&Post{}, orm.BelongsToConfig{})
}
func (c *Comment) Post() (Post, error) {
return orm.BelongsTo[Post](c).Get()
}
type Category struct {
ID int64
Title string
}
func (c Category) ConfigureEntity(e *orm.EntityConfigurator) {
e.Table("categories").BelongsToMany(Post{}, orm.BelongsToManyConfig{IntermediateTable: "post_categories"})
}
func (c Category) Posts() ([]Post, error) {
return orm.BelongsToMany[Post](c).All()
}
// enough models let's test
// Entities is mandatory
// Errors should be carried
func setup() error {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, body text, created_at TIMESTAMP, updated_at TIMESTAMP, deleted_at TIMESTAMP)`)
if err != nil {
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS emails (id INTEGER PRIMARY KEY, post_id INTEGER, email text)`)
if err != nil {
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS header_pictures (id INTEGER PRIMARY KEY, post_id INTEGER, link text)`)
if err != nil {
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, post_id INTEGER, body text)`)
if err != nil {
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY, title text)`)
if err != nil {
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS post_categories (post_id INTEGER, category_id INTEGER, PRIMARY KEY(post_id, category_id))`)
if err != nil {
return err
}
return orm.SetupConnections(orm.ConnectionConfig{
Name: "default",
DB: db,
Dialect: orm.Dialects.SQLite3,
Entities: []orm.Entity{&Post{}, &Comment{}, &Category{}, &HeaderPicture{}},
DatabaseValidations: true,
})
}
func TestFind(t *testing.T) {
err := setup()
assert.NoError(t, err)
err = orm.InsertAll(&Post{
BodyText: "my body for insert",
})
assert.NoError(t, err)
post, err := orm.Find[Post](1)
assert.NoError(t, err)
assert.Equal(t, "my body for insert", post.BodyText)
assert.Equal(t, int64(1), post.ID)
}
func TestInsert(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "my body for insert",
}
err = orm.Insert(post)
assert.NoError(t, err)
assert.Equal(t, int64(1), post.ID)
var p Post
assert.NoError(t,
orm.GetConnection("default").DB.QueryRow(`SELECT id, body FROM posts where id = ?`, 1).Scan(&p.ID, &p.BodyText))
assert.Equal(t, "my body for insert", p.BodyText)
}
func TestInsertAll(t *testing.T) {
err := setup()
assert.NoError(t, err)
post1 := &Post{
BodyText: "Body1",
}
post2 := &Post{
BodyText: "Body2",
}
post3 := &Post{
BodyText: "Body3",
}
err = orm.InsertAll(post1, post2, post3)
assert.NoError(t, err)
var counter int
assert.NoError(t, orm.GetConnection("default").DB.QueryRow(`SELECT count(id) FROM posts`).Scan(&counter))
assert.Equal(t, 3, counter)
}
func TestUpdateORM(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "my body for insert",
}
err = orm.Insert(post)
assert.NoError(t, err)
assert.Equal(t, int64(1), post.ID)
post.BodyText += " update text"
assert.NoError(t, orm.Update(post))
var body string
assert.NoError(t,
orm.GetConnection("default").DB.QueryRow(`SELECT body FROM posts where id = ?`, post.ID).Scan(&body))
assert.Equal(t, "my body for insert update text", body)
}
func TestDeleteORM(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "my body for insert",
}
err = orm.Insert(post)
assert.NoError(t, err)
assert.Equal(t, int64(1), post.ID)
assert.NoError(t, orm.Delete(post))
var count int
assert.NoError(t,
orm.GetConnection("default").DB.QueryRow(`SELECT count(id) FROM posts where id = ?`, post.ID).Scan(&count))
assert.Equal(t, 0, count)
}
func TestAdd_HasMany(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "my body for insert",
}
err = orm.Insert(post)
assert.NoError(t, err)
assert.Equal(t, int64(1), post.ID)
err = orm.Add(post, []orm.Entity{
Comment{
Body: "comment 1",
},
Comment{
Body: "comment 2",
},
}...)
// orm.Query(qm.WhereBetween())
assert.NoError(t, err)
var count int
assert.NoError(t, orm.GetConnection("default").DB.QueryRow(`SELECT COUNT(id) FROM comments`).Scan(&count))
assert.Equal(t, 2, count)
comment, err := orm.Find[Comment](1)
assert.NoError(t, err)
assert.Equal(t, int64(1), comment.PostID)
}
func TestAdd_ManyToMany(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "my body for insert",
}
err = orm.Insert(post)
assert.NoError(t, err)
assert.Equal(t, int64(1), post.ID)
err = orm.Add(post, []orm.Entity{
&Category{
Title: "cat 1",
},
&Category{
Title: "cat 2",
},
}...)
assert.NoError(t, err)
var count int
assert.NoError(t, orm.GetConnection("default").DB.QueryRow(`SELECT COUNT(post_id) FROM post_categories`).Scan(&count))
assert.Equal(t, 2, count)
assert.NoError(t, orm.GetConnection("default").DB.QueryRow(`SELECT COUNT(id) FROM categories`).Scan(&count))
assert.Equal(t, 2, count)
categories, err := post.Categories()
assert.NoError(t, err)
assert.Equal(t, 2, len(categories))
assert.Equal(t, int64(1), categories[0].ID)
assert.Equal(t, int64(2), categories[1].ID)
}
func TestSave(t *testing.T) {
t.Run("save should insert", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "1",
}
assert.NoError(t, orm.Save(post))
assert.Equal(t, int64(1), post.ID)
})
t.Run("save should update", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "1",
}
assert.NoError(t, orm.Save(post))
assert.Equal(t, int64(1), post.ID)
post.BodyText += "2"
assert.NoError(t, orm.Save(post))
myPost, err := orm.Find[Post](1)
assert.NoError(t, err)
assert.EqualValues(t, post.BodyText, myPost.BodyText)
})
}
func TestHasMany(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "first post",
}
assert.NoError(t, orm.Save(post))
assert.Equal(t, int64(1), post.ID)
assert.NoError(t, orm.Save(&Comment{
PostID: post.ID,
Body: "comment 1",
}))
assert.NoError(t, orm.Save(&Comment{
PostID: post.ID,
Body: "comment 2",
}))
comments, err := orm.HasMany[Comment](post).All()
assert.NoError(t, err)
assert.Len(t, comments, 2)
assert.Equal(t, post.ID, comments[0].PostID)
assert.Equal(t, post.ID, comments[1].PostID)
}
func TestBelongsTo(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "first post",
}
assert.NoError(t, orm.Save(post))
assert.Equal(t, int64(1), post.ID)
comment := &Comment{
PostID: post.ID,
Body: "comment 1",
}
assert.NoError(t, orm.Save(comment))
post2, err := orm.BelongsTo[Post](comment).Get()
assert.NoError(t, err)
assert.Equal(t, post.BodyText, post2.BodyText)
}
func TestHasOne(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "first post",
}
assert.NoError(t, orm.Save(post))
assert.Equal(t, int64(1), post.ID)
headerPicture := &HeaderPicture{
PostID: post.ID,
Link: "google",
}
assert.NoError(t, orm.Save(headerPicture))
c1, err := orm.HasOne[HeaderPicture](post).Get()
assert.NoError(t, err)
assert.Equal(t, headerPicture.PostID, c1.PostID)
}
func TestBelongsToMany(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "first Post",
}
assert.NoError(t, orm.Save(post))
assert.Equal(t, int64(1), post.ID)
category := &Category{
Title: "first category",
}
assert.NoError(t, orm.Save(category))
assert.Equal(t, int64(1), category.ID)
_, _, err = orm.ExecRaw[Category](`INSERT INTO post_categories (post_id, category_id) VALUES (?,?)`, post.ID, category.ID)
assert.NoError(t, err)
categories, err := orm.BelongsToMany[Category](post).All()
assert.NoError(t, err)
assert.Len(t, categories, 1)
}
func TestSchematic(t *testing.T) {
err := setup()
assert.NoError(t, err)
orm.Schematic()
}
func TestAddProperty(t *testing.T) {
t.Run("having pk value", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "first post",
}
assert.NoError(t, orm.Save(post))
assert.EqualValues(t, 1, post.ID)
err = orm.Add(post, &Comment{PostID: post.ID, Body: "firstComment"})
assert.NoError(t, err)
var comment Comment
assert.NoError(t, orm.GetConnection("default").
DB.
QueryRow(`SELECT id, post_id, body FROM comments WHERE post_id=?`, post.ID).
Scan(&comment.ID, &comment.PostID, &comment.Body))
assert.EqualValues(t, post.ID, comment.PostID)
})
t.Run("not having PK value", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
post := &Post{
BodyText: "first post",
}
assert.NoError(t, orm.Save(post))
assert.EqualValues(t, 1, post.ID)
err = orm.Add(post, &AuthorEmail{Email: "myemail"})
assert.NoError(t, err)
emails, err := orm.QueryRaw[AuthorEmail](`SELECT id, email FROM emails WHERE post_id=?`, post.ID)
assert.NoError(t, err)
assert.Equal(t, []AuthorEmail{{ID: 1, Email: "myemail"}}, emails)
})
}
func TestQuery(t *testing.T) {
t.Run("querying single row", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
// post, err := orm.Query[Post]().Where("id", 1).First()
post, err := orm.Query[Post]().WherePK(1).First().Get()
assert.NoError(t, err)
assert.EqualValues(t, "body 1", post.BodyText)
assert.EqualValues(t, 1, post.ID)
})
t.Run("querying multiple rows", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
assert.NoError(t, orm.Save(&Post{BodyText: "body 2"}))
assert.NoError(t, orm.Save(&Post{BodyText: "body 3"}))
posts, err := orm.Query[Post]().All()
assert.NoError(t, err)
assert.Len(t, posts, 3)
assert.Equal(t, "body 1", posts[0].BodyText)
})
t.Run("updating a row using query interface", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
affected, err := orm.Query[Post]().Where("id", 1).Set("body", "body jadid").Update()
assert.NoError(t, err)
assert.EqualValues(t, 1, affected)
post, err := orm.Find[Post](1)
assert.NoError(t, err)
assert.Equal(t, "body jadid", post.BodyText)
})
t.Run("deleting a row using query interface", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
affected, err := orm.Query[Post]().WherePK(1).Delete()
assert.NoError(t, err)
assert.EqualValues(t, 1, affected)
count, err := orm.Query[Post]().WherePK(1).Count().Get()
assert.NoError(t, err)
assert.EqualValues(t, 0, count)
})
t.Run("count", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
count, err := orm.Query[Post]().WherePK(1).Count().Get()
assert.NoError(t, err)
assert.EqualValues(t, 0, count)
})
t.Run("latest", func(t *testing.T) {
err := setup()
assert.NoError(t, err)
assert.NoError(t, orm.Save(&Post{BodyText: "body 1"}))
assert.NoError(t, orm.Save(&Post{BodyText: "body 2"}))
post, err := orm.Query[Post]().Latest().Get()
assert.NoError(t, err)
assert.EqualValues(t, "body 2", post.BodyText)
})
}
func TestSetup(t *testing.T) {
t.Run("tables are out of sync", func(t *testing.T) {
db, err := sql.Open("sqlite3", ":memory:")
// _, err = db.Exec(`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, body text, created_at TIMESTAMP, updated_at TIMESTAMP, deleted_at TIMESTAMP)`)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS emails (id INTEGER PRIMARY KEY, post_id INTEGER, email text)`)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS header_pictures (id INTEGER PRIMARY KEY, post_id INTEGER, link text)`)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, post_id INTEGER, body text)`)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY, title text)`)
// _, err = db.Exec(`CREATE TABLE IF NOT EXISTS post_categories (post_id INTEGER, category_id INTEGER, PRIMARY KEY(post_id, category_id))`)
err = orm.SetupConnections(orm.ConnectionConfig{
Name: "default",
DB: db,
Dialect: orm.Dialects.SQLite3,
Entities: []orm.Entity{&Post{}, &Comment{}, &Category{}, &HeaderPicture{}},
DatabaseValidations: true,
})
assert.Error(t, err)
})
t.Run("schemas are wrong", func(t *testing.T) {
db, err := sql.Open("sqlite3", ":memory:")
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, body text, created_at TIMESTAMP, updated_at TIMESTAMP, deleted_at TIMESTAMP)`)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS emails (id INTEGER PRIMARY KEY, post_id INTEGER, email text)`)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS header_pictures (id INTEGER PRIMARY KEY, post_id INTEGER, link text)`)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, body text)`) // missing post_id
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY, title text)`)
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS post_categories (post_id INTEGER, category_id INTEGER, PRIMARY KEY(post_id, category_id))`)
err = orm.SetupConnections(orm.ConnectionConfig{
Name: "default",
DB: db,
Dialect: orm.Dialects.SQLite3,
Entities: []orm.Entity{&Post{}, &Comment{}, &Category{}, &HeaderPicture{}},
DatabaseValidations: true,
})
assert.Error(t, err)
})
}