summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2024-11-21 11:04:08 -0300
committerEuAndreh <eu@euandre.org>2025-01-17 09:51:33 -0300
commit65de65ce1e34efeb421974bcb5ddd85fb53253bb (patch)
tree62c37d832885d9a1113369db4e27563c4dbbcdb8 /tests
parentsrc/papod.go: Integrate db layer with network, create command handlers, simpl... (diff)
downloadpapod-65de65ce1e34efeb421974bcb5ddd85fb53253bb.tar.gz
papod-65de65ce1e34efeb421974bcb5ddd85fb53253bb.tar.xz
Implement most of db layer
Many missing implementations or tests are marked with FIXME so I don't loose track of holes in the code.
Diffstat (limited to 'tests')
-rw-r--r--tests/papod.go4530
-rw-r--r--tests/queries.sql1081
2 files changed, 4987 insertions, 624 deletions
diff --git a/tests/papod.go b/tests/papod.go
index 42974f4..283017a 100644
--- a/tests/papod.go
+++ b/tests/papod.go
@@ -19,6 +19,261 @@ import (
+type userChangeT struct{
+ id int64
+ timestamp time.Time
+ user_id int64
+ attribute string
+ valueStr *string
+ valueBlob *guuid.UUID
+ valueBool *bool
+ op bool
+}
+
+type networkChangeT struct{
+ id int64
+ timestamp time.Time
+ network_id int64
+ attribute string
+ value string
+ op bool
+}
+
+type channelChangeT struct{
+ id int64
+ timestamp time.Time
+ channel_id int64
+ attribute string
+ value string
+ op bool
+}
+
+
+
+func userChangesSQL(prefix string) string {
+ const tmpl = `
+ SELECT
+ "%s_user_changes".id,
+ "%s_user_changes".timestamp,
+ "%s_user_changes".user_id,
+ "%s_user_changes".attribute,
+ "%s_user_changes".value_text,
+ "%s_user_changes".value_blob,
+ "%s_user_changes".value_bool,
+ "%s_user_changes".op
+ FROM "%s_user_changes"
+ JOIN "%s_users" ON
+ "%s_user_changes".user_id = "%s_users".id
+ WHERE "%s_users".user_uuid = ?
+ ORDER BY "%s_user_changes".id ASC;
+ `
+ return fmt.Sprintf(
+ tmpl,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ )
+}
+
+func makeUserChanges(db *sql.DB, prefix string) func(guuid.UUID) []userChangeT {
+ q := userChangesSQL(prefix)
+
+ return func(userID guuid.UUID) []userChangeT {
+ userChanges := []userChangeT{}
+ rows, err := db.Query(q, userID[:])
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ for rows.Next() {
+ userChange := userChangeT{}
+ var (
+ timestr string
+ value_bytes []byte
+ )
+ err := rows.Scan(
+ &userChange.id,
+ &timestr,
+ &userChange.user_id,
+ &userChange.attribute,
+ &userChange.valueStr,
+ &value_bytes,
+ &userChange.valueBool,
+ &userChange.op,
+ )
+ g.TErrorIf(err)
+
+ if value_bytes != nil {
+ valueBlob := guuid.UUID(value_bytes)
+ userChange.valueBlob = &valueBlob
+ }
+
+ userChange.timestamp, err = time.Parse(
+ time.RFC3339Nano,
+ timestr,
+ )
+ g.TErrorIf(err)
+
+ userChanges = append(userChanges, userChange)
+ }
+
+ g.TErrorIf(rows.Err())
+ return userChanges
+ }
+}
+
+func networkChangesSQL(prefix string) string {
+ const tmpl = `
+ SELECT
+ "%s_network_changes".id,
+ "%s_network_changes".timestamp,
+ "%s_network_changes".network_id,
+ "%s_network_changes".attribute,
+ "%s_network_changes".value,
+ "%s_network_changes".op
+ FROM "%s_network_changes"
+ JOIN "%s_networks" ON
+ "%s_network_changes".network_id = "%s_networks".id
+ WHERE "%s_networks".uuid = ?
+ ORDER BY "%s_network_changes".id ASC;
+ `
+ return fmt.Sprintf(
+ tmpl,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ prefix,
+ )
+}
+
+func makeNetworkChanges(
+ db *sql.DB,
+ prefix string,
+) func(guuid.UUID) []networkChangeT {
+ q := networkChangesSQL(prefix)
+
+ return func(networkID guuid.UUID) []networkChangeT {
+ networkChanges := []networkChangeT{}
+ rows, err := db.Query(q, networkID[:])
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ for rows.Next() {
+ networkChange := networkChangeT{}
+ var timestr string
+ err := rows.Scan(
+ &networkChange.id,
+ &timestr,
+ &networkChange.network_id,
+ &networkChange.attribute,
+ &networkChange.value,
+ &networkChange.op,
+ )
+ g.TErrorIf(err)
+
+ networkChange.timestamp, err = time.Parse(
+ time.RFC3339Nano,
+ timestr,
+ )
+ g.TErrorIf(err)
+
+ networkChanges = append(networkChanges, networkChange)
+ }
+
+ g.TErrorIf(rows.Err())
+ return networkChanges
+ }
+}
+
+func channelChangesSQL(prefix string) string {
+ const tmpl = `
+ SELECT
+ id,
+ timestamp,
+ channel_id,
+ attribute,
+ value_text,
+ value_bool,
+ op
+ FROM "%s_channel_changes"
+ WHERE channel_id = ?
+ ORDER BY id ASC;
+ `
+ return fmt.Sprintf(tmpl, prefix)
+}
+
+func makeChannelChanges(
+ db *sql.DB,
+ prefix string,
+) func(int64) []channelChangeT {
+ q := channelChangesSQL(prefix)
+
+ return func(id int64) []channelChangeT {
+ channelChanges := []channelChangeT{}
+ rows, err := db.Query(q, id)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ for rows.Next() {
+ channelChange := channelChangeT{}
+ var (
+ timestr string
+ valueString sql.NullString
+ valueBool sql.NullBool
+ )
+ err := rows.Scan(
+ &channelChange.id,
+ &timestr,
+ &channelChange.channel_id,
+ &channelChange.attribute,
+ &valueString,
+ &valueBool,
+ &channelChange.op,
+ )
+ g.TErrorIf(err)
+
+ if valueString.Valid {
+ channelChange.value = valueString.String
+ } else if valueBool.Valid {
+ if valueBool.Bool {
+ channelChange.value = "true"
+ } else {
+ channelChange.value = "false"
+ }
+ }
+
+ channelChange.timestamp, err = time.Parse(
+ time.RFC3339Nano,
+ timestr,
+ )
+ g.TErrorIf(err)
+
+ channelChanges = append(channelChanges, channelChange)
+ }
+
+ g.TErrorIf(rows.Err())
+ return channelChanges
+ }
+}
+
func mknstring(n int) string {
buffer := make([]byte, n)
_, err := io.ReadFull(rand.Reader, buffer)
@@ -39,14 +294,6 @@ func test_defaultPrefix() {
})
}
-func test_serialized() {
- // FIXME
-}
-
-func test_execSerialized() {
- // FIXME
-}
-
func test_tryRollback() {
g.TestStart("tryRollback()")
@@ -143,7 +390,7 @@ func test_createUserStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -159,6 +406,8 @@ func test_createUserStmt() {
db.Close,
)
+ userChanges := makeUserChanges(db, prefix)
+
g.Testing("userID's must be unique", func() {
newUser := newUserT{
@@ -223,6 +472,61 @@ func test_createUserStmt() {
g.TErrorIf(err2)
})
+ g.Testing("new user trigger inserts into *_user_changes", func() {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ username: mkstring(),
+ displayName: mkstring(),
+ }
+
+ _, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ changes := userChanges(newUser.uuid)
+ g.TAssertEqual(len(changes), 3)
+ g.TAssertEqual(
+ []string{
+ changes[0].attribute,
+ changes[1].attribute,
+ changes[2].attribute,
+ *changes[0].valueStr,
+ *changes[1].valueStr,
+ },
+ []string{
+ "username",
+ "display_name",
+ "deleted",
+ newUser.username,
+ newUser.displayName,
+ },
+ )
+ g.TAssertEqual(*changes[2].valueBool, false)
+ g.TAssertEqual(
+ []bool{
+ changes[0].op,
+ changes[1].op,
+ changes[2].op,
+ changes[0].valueBlob == nil,
+ changes[0].valueBool == nil,
+ changes[1].valueBlob == nil,
+ changes[1].valueBool == nil,
+ changes[2].valueStr == nil,
+ changes[2].valueBlob == nil,
+ },
+ []bool{
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ },
+ )
+ })
+
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
createUserClose(),
@@ -240,7 +544,7 @@ func test_userByUUIDStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -326,7 +630,7 @@ func test_updateUserStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -366,10 +670,12 @@ func test_updateUserStmt() {
return user
}
+ userChanges := makeUserChanges(db, prefix)
+
g.Testing("a user needs to exist to be updated", func() {
virtualUser := userT{
- id: 1234,
+ id: 1234,
}
g.TAssertEqual(updateUser(virtualUser), sql.ErrNoRows)
@@ -452,7 +758,7 @@ func test_updateUserStmt() {
user2 := user1
user2.timestamp = user2.timestamp.Add(time.Minute * 1)
user2.uuid = guuid.New()
- err = updateUser(user2)
+ err := updateUser(user2)
g.TErrorIf(err)
user3, err := userByUUID(user1.uuid)
@@ -460,6 +766,154 @@ func test_updateUserStmt() {
g.TAssertEqual(user3, user1)
})
+ g.Testing("no extra writes to *_user_changes when not updated", func() {
+ user := create()
+ g.TAssertEqual(len(userChanges(user.uuid)), 3)
+
+ err := updateUser(user)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(userChanges(user.uuid)), 3)
+ })
+
+ g.Testing("new username end up in *_user_changes when updated", func() {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ username: "first username",
+ displayName: "display name",
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(userChanges(user.uuid)), 3)
+
+ user.username = "second username"
+ g.TErrorIf(updateUser(user))
+
+ changes := userChanges(user.uuid)[3:]
+ g.TAssertEqual(len(changes), 2)
+ g.TAssertEqual(
+ []string{
+ changes[0].attribute,
+ changes[1].attribute,
+ *changes[0].valueStr,
+ *changes[1].valueStr,
+ },
+ []string{
+ "username",
+ "username",
+ "first username",
+ "second username",
+ },
+ )
+ g.TAssertEqual(
+ []bool{
+ changes[0].op,
+ changes[1].op,
+ changes[0].valueBlob == nil,
+ changes[0].valueBool == nil,
+ changes[1].valueBlob == nil,
+ changes[1].valueBool == nil,
+ },
+ []bool{
+ false,
+ true,
+ true,
+ true,
+ true,
+ true,
+ },
+ )
+ })
+
+ g.Testing("displayName end up in *_user_changes when updated", func() {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ username: "username",
+ displayName: "first display name",
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(userChanges(user.uuid)), 3)
+
+ user.displayName = "second display name"
+ g.TErrorIf(updateUser(user))
+ changes := userChanges(user.uuid)[3:]
+ g.TAssertEqual(len(changes), 2)
+ g.TAssertEqual(
+ []string{
+ changes[0].attribute,
+ changes[1].attribute,
+ *changes[0].valueStr,
+ *changes[1].valueStr,
+ },
+ []string{
+ "display_name",
+ "display_name",
+ "first display name",
+ "second display name",
+ },
+ )
+ g.TAssertEqual(
+ []bool{
+ changes[0].op,
+ changes[1].op,
+ changes[0].valueBlob == nil,
+ changes[0].valueBool == nil,
+ changes[1].valueBlob == nil,
+ changes[1].valueBool == nil,
+ },
+ []bool{
+ false,
+ true,
+ true,
+ true,
+ true,
+ true,
+ },
+ )
+ })
+
+ g.Testing("pictureID end up in *_user_changes when updated", func() {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ username: "username",
+ displayName: "first display name",
+ }
+ pictureID := guuid.New()
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(userChanges(user.uuid)), 3)
+
+ user.pictureID = &pictureID
+ g.TErrorIf(updateUser(user))
+ changes := userChanges(user.uuid)[3:]
+ g.TAssertEqual(len(changes), 1)
+ g.TAssertEqual(changes[0].attribute, "picture_uuid")
+ g.TAssertEqual(*changes[0].valueBlob, pictureID)
+ g.TAssertEqual(changes[0].op, true)
+
+ user.pictureID = nil
+ g.TErrorIf(updateUser(user))
+ changes = userChanges(user.uuid)[4:]
+ g.TAssertEqual(len(changes), 1)
+ g.TAssertEqual(changes[0].attribute, "picture_uuid")
+ g.TAssertEqual(*changes[0].valueBlob, pictureID)
+ g.TAssertEqual(
+ []bool{
+ changes[0].op,
+ changes[0].valueStr == nil,
+ changes[0].valueBool == nil,
+ },
+ []bool{
+ false,
+ true,
+ true,
+ },
+ )
+ })
+
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
updateUserClose(),
@@ -477,7 +931,7 @@ func test_deleteUserStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -495,8 +949,11 @@ func test_deleteUserStmt() {
defer g.SomeFnError(
createUserClose,
deleteUserClose,
+ db.Close,
)
+ userChanges := makeUserChanges(db, prefix)
+
g.Testing("a user needs to exist to be deleted", func() {
err := deleteUser(guuid.New())
@@ -517,6 +974,49 @@ func test_deleteUserStmt() {
g.TAssertEqual(err2, sql.ErrNoRows)
})
+ g.Testing("deletion triggers insertion into *_user_changes", func() {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(userChanges(user.uuid)), 3)
+
+ g.TErrorIf(deleteUser(user.uuid))
+ changes := userChanges(user.uuid)[3:]
+ g.TAssertEqual(len(changes), 2)
+ g.TAssertEqual(
+ []string{
+ changes[0].attribute,
+ changes[1].attribute,
+ },
+ []string{ "deleted", "deleted" },
+ )
+ g.TAssertEqual(
+ []bool{
+ *changes[0].valueBool,
+ changes[0].op,
+ changes[0].valueStr == nil,
+ changes[0].valueBlob == nil,
+ *changes[1].valueBool,
+ changes[1].op,
+ changes[1].valueStr == nil,
+ changes[1].valueBlob == nil,
+ },
+ []bool{
+ false,
+ false,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ },
+ )
+ })
+
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
deleteUserClose(),
@@ -527,7 +1027,6 @@ func test_deleteUserStmt() {
}
func test_addNetworkStmt() {
- return // FIXME
g.TestStart("addNetworkStmt()")
const (
@@ -535,7 +1034,7 @@ func test_addNetworkStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -547,15 +1046,22 @@ func test_addNetworkStmt() {
createUser, createUserClose, createUserErr := createUserStmt(cfg)
deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg)
addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ members, membersClose, membersErr := membersStmt(cfg)
g.TErrorIf(g.SomeError(
createUserErr,
deleteUserErr,
addNetworkErr,
+ membershipErr,
+ membersErr,
))
defer g.SomeFnError(
createUserClose,
deleteUserClose,
addNetworkClose,
+ membershipClose,
+ membersClose,
+ db.Close,
)
create := func() userT {
@@ -569,8 +1075,25 @@ func test_addNetworkStmt() {
return user
}
+ allMembers := func(actor memberT, networkID guuid.UUID) []memberT {
+ rows, err := members(actor)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ var members []memberT
+ err = memberEach(rows, func(member memberT) error {
+ members = append(members, member)
+ return nil
+ })
+ g.TErrorIf(err)
+
+ return members
+ }
+
+ networkChanges := makeNetworkChanges(db, prefix)
- g.Testing("a user can create a newtwork", func() {
+
+ g.Testing("a user can create a network", func() {
creator := create()
newNetwork := newNetworkT{
@@ -580,13 +1103,12 @@ func test_addNetworkStmt() {
type_: NetworkType_Unlisted,
}
- network, err := addNetwork(creator, newNetwork)
+ network, err := addNetwork(creator, newNetwork, guuid.New())
g.TErrorIf(err)
g.TAssertEqual(network.id == 0, false)
g.TAssertEqual(network.timestamp == time.Time{}, false)
g.TAssertEqual(network.uuid, newNetwork.uuid)
- g.TAssertEqual(network.createdBy, creator.uuid)
g.TAssertEqual(network.name, "the network name")
g.TAssertEqual(network.description, "the network description")
g.TAssertEqual(network.type_, NetworkType_Unlisted)
@@ -594,14 +1116,15 @@ func test_addNetworkStmt() {
g.Testing("the creator needs to exist", func() {
newNetwork := newNetworkT{
- uuid: guuid.New(),
+ uuid: guuid.New(),
+ type_: NetworkType_Unlisted,
}
virtualUser := userT{
- uuid: guuid.New(),
+ id: 1234,
}
- _, err := addNetwork(virtualUser, newNetwork)
+ _, err := addNetwork(virtualUser, newNetwork, guuid.New())
g.TAssertEqual(
err.(golite.Error).ExtendedCode,
golite.ErrConstraintNotNull,
@@ -612,12 +1135,13 @@ func test_addNetworkStmt() {
creator := create()
newNetwork := newNetworkT{
- uuid: guuid.New(),
- name: mkstring(),
+ uuid: guuid.New(),
+ name: mkstring(),
+ type_: NetworkType_Unlisted,
}
- _, err1 := addNetwork(creator, newNetwork)
- _, err2 := addNetwork(creator, newNetwork)
+ _, err1 := addNetwork(creator, newNetwork, guuid.New())
+ _, err2 := addNetwork(creator, newNetwork, guuid.New())
g.TErrorIf(err1)
g.TAssertEqual(
err2.(golite.Error).ExtendedCode,
@@ -629,43 +1153,130 @@ func test_addNetworkStmt() {
creator := create()
newNetwork1 := newNetworkT{
- uuid: guuid.New(),
+ uuid: guuid.New(),
+ type_: NetworkType_Unlisted,
}
newNetwork2 := newNetworkT{
- uuid: guuid.New(),
+ uuid: guuid.New(),
+ type_: NetworkType_Unlisted,
}
- network1, err1 := addNetwork(creator, newNetwork1)
- network2, err2 := addNetwork(creator, newNetwork2)
+ _, err1 := addNetwork(creator, newNetwork1, guuid.New())
+ _, err2 := addNetwork(creator, newNetwork2, guuid.New())
g.TErrorIf(err1)
g.TErrorIf(err2)
-
- g.TAssertEqual(network1.createdBy, creator.uuid)
- g.TAssertEqual(network2.createdBy, creator.uuid)
})
g.Testing("a deleted user can't create a network", func() {
creator := create()
newNetwork1 := newNetworkT{
- uuid: guuid.New(),
+ uuid: guuid.New(),
+ type_: NetworkType_Unlisted,
}
newNetwork2 := newNetworkT{
- uuid: guuid.New(),
+ uuid: guuid.New(),
+ type_: NetworkType_Unlisted,
}
- _, err := addNetwork(creator, newNetwork1)
+ _, err := addNetwork(creator, newNetwork1, guuid.New())
g.TErrorIf(err)
err = deleteUser(creator.uuid)
g.TErrorIf(err)
- _, err = addNetwork(creator, newNetwork2)
+ _, err = addNetwork(creator, newNetwork2, guuid.New())
g.TAssertEqual(
err.(golite.Error).ExtendedCode,
golite.ErrConstraintNotNull,
)
})
+ g.Testing("new network triggers inserts to the changes table", func() {
+ creator := create()
+
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ name: "the network name",
+ description: "the network description",
+ type_: NetworkType_Unlisted,
+ }
+
+ _, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ changes := networkChanges(newNetwork.uuid)
+ g.TAssertEqual(len(changes), 4)
+ g.TAssertEqual(
+ []string{
+ changes[0].attribute,
+ changes[1].attribute,
+ changes[2].attribute,
+ changes[3].attribute,
+ changes[0].value,
+ changes[1].value,
+ changes[2].value,
+ changes[3].value,
+ },
+ []string{
+ "name",
+ "description",
+ "type",
+ "deleted",
+ "the network name",
+ "the network description",
+ "unlisted",
+ "0",
+ },
+ )
+ g.TAssertEqual(
+ []bool{
+ changes[0].op,
+ changes[1].op,
+ changes[2].op,
+ },
+ []bool{ true, true, true },
+ )
+ })
+
+ g.Testing("the creator is automatically a member", func() {
+ creator := create()
+ memberID := guuid.New()
+
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Unlisted,
+ }
+
+ network, err := addNetwork(creator, newNetwork, memberID)
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ members := allMembers(member, network.uuid)
+ g.TAssertEqual(len(members), 1)
+ g.TAssertEqual(members[0].uuid, memberID)
+ g.TAssertEqual(members[0].status, MemberStatus_Active)
+ })
+
+ g.Testing(`the creator has "creator" and "admin" roles`, func() {
+ creator := create()
+ memberID := guuid.New()
+
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Unlisted,
+ }
+
+ network, err := addNetwork(creator, newNetwork, memberID)
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ g.TAssertEqual(member.roles, []string{"admin", "creator"})
+ })
+
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
addNetworkClose(),
@@ -676,7 +1287,6 @@ func test_addNetworkStmt() {
}
func test_getNetworkStmt() {
- return // FIXME
g.TestStart("getNetworkStmt()")
const (
@@ -684,7 +1294,7 @@ func test_getNetworkStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -697,6 +1307,7 @@ func test_getNetworkStmt() {
deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg)
addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
getNetwork, getNetworkClose, getNetworkErr := getNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
dropMember, dropMemberClose, dropMemberErr := dropMemberStmt(cfg)
g.TErrorIf(g.SomeError(
@@ -704,6 +1315,7 @@ func test_getNetworkStmt() {
deleteUserErr,
addNetworkErr,
getNetworkErr,
+ membershipErr,
addMemberErr,
dropMemberErr,
))
@@ -712,8 +1324,10 @@ func test_getNetworkStmt() {
deleteUserClose,
addNetworkClose,
getNetworkClose,
+ membershipClose,
addMemberClose,
dropMemberClose,
+ db.Close,
)
create := func() userT {
@@ -727,22 +1341,25 @@ func test_getNetworkStmt() {
return user
}
- add := func(user userT, type_ NetworkType) networkT {
+ add := func(user userT, type_ NetworkType) (networkT, memberT) {
newNetwork := newNetworkT{
uuid: guuid.New(),
type_: type_,
}
- network, err := addNetwork(user, newNetwork)
+ network, err := addNetwork(user, newNetwork, guuid.New())
g.TErrorIf(err)
- return network
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return network, member
}
g.Testing("what we get is the same that was created", func() {
creator := create()
- network1 := add(creator, NetworkType_Public)
+ network1, _ := add(creator, NetworkType_Public)
network2, err := getNetwork(creator, network1.uuid)
g.TErrorIf(err)
@@ -754,9 +1371,9 @@ func test_getNetworkStmt() {
g.TAssertEqual(err, sql.ErrNoRows)
})
- g.Testing("the probing user needs to exist", func() {
+ g.Testing("the probing member needs to exist", func() {
creator := create()
- network := add(creator, NetworkType_Public)
+ network, _ := add(creator, NetworkType_Public)
virtualUser := userT{
id: 1234,
@@ -772,7 +1389,7 @@ func test_getNetworkStmt() {
g.Testing("the probing user can see any public network", func() {
creator := create()
user := create()
- network := add(creator, NetworkType_Public)
+ network, _ := add(creator, NetworkType_Public)
network1, err1 := getNetwork(creator, network.uuid)
network2, err2 := getNetwork(user, network.uuid)
@@ -785,7 +1402,7 @@ func test_getNetworkStmt() {
g.Testing("the probing user sees the given unlisted network", func() {
creator := create()
user := create()
- network := add(creator, NetworkType_Unlisted)
+ network, _ := add(creator, NetworkType_Unlisted)
network1, err1 := getNetwork(creator, network.uuid)
network2, err2 := getNetwork(user, network.uuid)
@@ -798,7 +1415,7 @@ func test_getNetworkStmt() {
g.Testing("the probing user can't see a private network", func() {
creator := create()
user := create()
- network := add(creator, NetworkType_Private)
+ network, _ := add(creator, NetworkType_Private)
_, err1 := getNetwork(creator, network.uuid)
_, err2 := getNetwork(user, network.uuid)
@@ -808,17 +1425,19 @@ func test_getNetworkStmt() {
g.Testing("the probing user must be a member to see it", func() {
creator := create()
- member := create()
- network := add(creator, NetworkType_Private)
+ user := create()
+ network, member := add(creator, NetworkType_Private)
newMember := newMemberT{
- userID: member.uuid,
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
}
- _, err := addMember(creator, network, newMember)
+ _, err := addMember(member, newMember)
g.TErrorIf(err)
network1, err1 := getNetwork(creator, network.uuid)
- network2, err2 := getNetwork(member, network.uuid)
+ network2, err2 := getNetwork(user, network.uuid)
g.TErrorIf(err1)
g.TErrorIf(err2)
g.TAssertEqual(network1, network)
@@ -827,13 +1446,15 @@ func test_getNetworkStmt() {
g.Testing("we can get the network if the creator was deleted", func() {
creator := create()
- member := create()
- network := add(creator, NetworkType_Public)
+ user := create()
+ network, member := add(creator, NetworkType_Public)
newMember := newMemberT{
- userID: member.uuid,
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
}
- _, err := addMember(creator, network, newMember)
+ _, err := addMember(member, newMember)
g.TErrorIf(err)
network1, err := getNetwork(creator, network.uuid)
@@ -842,14 +1463,14 @@ func test_getNetworkStmt() {
err = deleteUser(creator.uuid)
g.TErrorIf(err)
- network2, err := getNetwork(member, network.uuid)
+ network2, err := getNetwork(user, network.uuid)
g.TErrorIf(err)
g.TAssertEqual(network2, network1)
})
g.Testing("a deleted creator can't get a network", func() {
creator := create()
- network := add(creator, NetworkType_Public)
+ network, _ := add(creator, NetworkType_Public)
_, err := getNetwork(creator, network.uuid)
g.TErrorIf(err)
@@ -863,7 +1484,7 @@ func test_getNetworkStmt() {
g.Testing("a deleted user can't get a public network", func() {
user := create()
- network := add(create(), NetworkType_Public)
+ network, _ := add(create(), NetworkType_Public)
_, err := getNetwork(user, network.uuid)
g.TErrorIf(err)
@@ -876,41 +1497,51 @@ func test_getNetworkStmt() {
})
g.Testing("a deleted member can't get a private network", func() {
- creator := create()
- member := create()
- network := add(creator, NetworkType_Private)
+ creator := create()
+ user := create()
+ network, member := add(creator, NetworkType_Private)
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
- _, err := getNetwork(member, network.uuid)
+ _, err := addMember(member, newMember)
g.TErrorIf(err)
- err = deleteUser(member.uuid)
+ _, err = getNetwork(user, network.uuid)
g.TErrorIf(err)
- _, err = getNetwork(member, network.uuid)
+ err = deleteUser(user.uuid)
+ g.TErrorIf(err)
+
+ _, err = getNetwork(user, network.uuid)
g.TAssertEqual(err, sql.ErrNoRows)
})
g.Testing("a removed member can't get a private network", func() {
creator := create()
- member := create()
- network := add(creator, NetworkType_Private)
+ user := create()
+ network, member := add(creator, NetworkType_Private)
newMember := newMemberT{
- userID: member.uuid,
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
}
- _, err := getNetwork(member, network.uuid)
+ _, err := getNetwork(user, network.uuid)
g.TAssertEqual(err, sql.ErrNoRows)
- _, err = addMember(creator, network, newMember)
+ _, err = addMember(member, newMember)
g.TErrorIf(err)
- _, err = getNetwork(member, network.uuid)
+ _, err = getNetwork(user, network.uuid)
g.TErrorIf(err)
- err = dropMember(creator, member.uuid)
+ err = dropMember(member, newMember.memberID)
g.TErrorIf(err)
- _, err = getNetwork(member, network.uuid)
+ _, err = getNetwork(user, network.uuid)
g.TAssertEqual(err, sql.ErrNoRows)
})
@@ -924,12 +1555,181 @@ func test_getNetworkStmt() {
}
func test_networkEach() {
- // FIXME
+ g.TestStart("networkEach()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ networks, networksClose, networksErr := networksStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ networksErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ networksClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func(user userT) guuid.UUID {
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ _, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ return newNetwork.uuid
+ }
+
+
+ g.Testing("callback is not called on empty set", func() {
+ rows, err := networks(create())
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ networkEach(rows, func(networkT) error {
+ g.Unreachable()
+ return nil
+ })
+ })
+
+ g.Testing("the callback is called once for each entry", func() {
+ creator := create()
+ networkIDs := []guuid.UUID{
+ add(creator),
+ add(creator),
+ add(creator),
+ }
+
+ rows, err := networks(creator)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ var collectedIDs[]guuid.UUID
+ err = networkEach(rows, func(network networkT) error {
+ collectedIDs = append(collectedIDs, network.uuid)
+ return nil
+ })
+ g.TErrorIf(err)
+
+ g.TAssertEqual(collectedIDs, networkIDs)
+ })
+
+ g.Testing("we halt if the timestamp is ill-formatted", func() {
+ creator := create()
+
+ add(creator)
+ add(creator)
+ networkID := add(creator)
+ add(creator)
+
+ const tmpl = `
+ UPDATE "%s_networks"
+ SET timestamp = %s
+ WHERE uuid = ?;
+ `
+ q1 := fmt.Sprintf(tmpl, prefix, "'01/01/1970'")
+ _, err := db.Exec(q1, networkID[:])
+ g.TErrorIf(err)
+
+ rows, err := networks(creator)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ n := 0
+ err = networkEach(rows, func(networkT) error {
+ n++
+ return nil
+ })
+
+ g.TAssertEqual(
+ err,
+ &time.ParseError{
+ Layout: time.RFC3339Nano,
+ Value: "01/01/1970",
+ LayoutElem: "2006",
+ ValueElem: "01/01/1970",
+ Message: "",
+ },
+ )
+ g.TAssertEqual(n, 5)
+
+ q2 := fmt.Sprintf(tmpl, prefix, g.SQLiteNow)
+ _, err = db.Exec(q2, networkID[:])
+ g.TErrorIf(err)
+ })
+
+ g.Testing("we halt if the callback returns an error", func() {
+ creator := create()
+ myErr := errors.New("callback error early return")
+
+ rows1, err := networks(creator)
+ g.TErrorIf(err)
+ defer rows1.Close()
+
+ n1 := 0
+ err1 := networkEach(rows1, func(networkT) error {
+ n1++
+ if n1 == 3 {
+ return myErr
+ }
+ return nil
+ })
+
+ rows2, err := networks(creator)
+ g.TErrorIf(err)
+ defer rows2.Close()
+
+ n2 := 0
+ err2 := networkEach(rows2, func(networkT) error {
+ n2++
+ return nil
+ })
+
+ g.TAssertEqual(err1, myErr)
+ g.TErrorIf(err2)
+ g.TAssertEqual(n1, 3)
+ g.TAssertEqual(n2, 7)
+ })
+
+ g.Testing("noop when given nil for *sql.Rows", func() {
+ err := networkEach(nil, func(networkT) error {
+ g.Unreachable()
+ return nil
+ })
+ g.TErrorIf(err)
+ })
}
func test_networksStmt() {
- /*
- FIXME
g.TestStart("networksStmt()")
const (
@@ -937,55 +1737,153 @@ func test_networksStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg)
addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
networks, networksClose, networksErr := networksStmt(cfg)
g.TErrorIf(g.SomeError(
+ createUserErr,
+ deleteUserErr,
addNetworkErr,
networksErr,
))
defer g.SomeFnError(
+ createUserClose,
+ deleteUserClose,
addNetworkClose,
networksClose,
db.Close,
)
- nets := func(user userT) []networkT {
- rows, err := networks(user)
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func(user userT, type_ NetworkType) networkT {
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: type_,
+ }
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
g.TErrorIf(err)
+ return network
+ }
+
+ allNetworks := func(user userT) ([]networkT, error) {
+ rows, err := networks(user)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
networkList := []networkT{}
err = networkEach(rows, func(network networkT) error {
networkList = append(networkList, network)
return nil
})
- g.TErrorIf(err)
+ if err != nil {
+ return nil, err
+ }
- return networkList
+ return networkList, nil
}
- g.Testing("when there are no networks, we get none", func() {
- // FIXME
+ g.Testing("when there are no networks, we get 0", func() {
+ networks, err := allNetworks(create())
+ g.TErrorIf(err)
+ g.TAssertEqual(len(networks), 0)
})
g.Testing("if we have only private networks, we also get none", func() {
- // FIXME
+ creator := create()
+ add(creator, NetworkType_Private)
+ add(creator, NetworkType_Private)
+ add(creator, NetworkType_Private)
+
+ networks, err := allNetworks(create())
+ g.TErrorIf(err)
+ g.TAssertEqual(len(networks), 0)
+ })
+
+ g.Testing("when only unlisted networks, we also get none", func() {
+ creator := create()
+ add(creator, NetworkType_Unlisted)
+ add(creator, NetworkType_Unlisted)
+ add(creator, NetworkType_Unlisted)
+
+ networks, err := allNetworks(create())
+ g.TErrorIf(err)
+ g.TAssertEqual(len(networks), 0)
})
g.Testing("we can get a list of public networks", func() {
- // FIXME
+ creator := create()
+ expected := []networkT{
+ add(creator, NetworkType_Public),
+ add(creator, NetworkType_Public),
+ add(creator, NetworkType_Public),
+ }
+
+ networks, err := allNetworks(create())
+ g.TErrorIf(err)
+ g.TAssertEqual(networks, expected)
})
g.Testing("a member user can see their's private networks", func() {
- // FIXME
+ creator1 := create()
+ creator2 := create()
+ add(creator1, NetworkType_Private)
+ add(creator1, NetworkType_Private)
+ add(creator1, NetworkType_Private)
+ add(creator2, NetworkType_Private)
+ add(creator2, NetworkType_Private)
+ add(creator2, NetworkType_Private)
+
+ networks, err := allNetworks(creator2)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(networks), 6)
})
- g.Testing("unlisted networks aren't shown", func() {
- // FIXME
+ g.Testing("a member user can see their's unlisted networks", func() {
+ creator1 := create()
+ creator2 := create()
+ add(creator1, NetworkType_Unlisted)
+ add(creator1, NetworkType_Unlisted)
+ add(creator1, NetworkType_Unlisted)
+ add(creator2, NetworkType_Unlisted)
+ add(creator2, NetworkType_Unlisted)
+ add(creator2, NetworkType_Unlisted)
+
+ networks, err := allNetworks(creator2)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(networks), 6)
+ })
+
+ g.Testing("a deleted user can't list anything", func() {
+ creator := create()
+ g.TErrorIf(deleteUser(creator.uuid))
+
+ _, err := allNetworks(creator)
+ g.TAssertEqual(err, sql.ErrNoRows)
})
g.Testing("no error if closed more than once", func() {
@@ -995,11 +1893,9 @@ func test_networksStmt() {
networksClose(),
))
})
- */
}
func test_setNetworkStmt() {
- return // FIXME
g.TestStart("setNetworkStmt()")
const (
@@ -1007,7 +1903,7 @@ func test_setNetworkStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -1020,17 +1916,35 @@ func test_setNetworkStmt() {
addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
getNetwork, getNetworkClose, getNetworkErr := getNetworkStmt(cfg)
setNetwork, setNetworkClose, setNetworkErr := setNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ addRole, addRoleClose, addRoleErr := addRoleStmt(cfg)
+ dropRole, dropRoleClose, dropRoleErr := dropRoleStmt(cfg)
+ editMember, editMemberClose, editMemberErr := editMemberStmt(cfg)
+ dropMember, dropMemberClose, dropMemberErr := dropMemberStmt(cfg)
g.TErrorIf(g.SomeError(
createUserErr,
addNetworkErr,
getNetworkErr,
setNetworkErr,
+ membershipErr,
+ addMemberErr,
+ addRoleErr,
+ dropRoleErr,
+ editMemberErr,
+ dropMemberErr,
))
defer g.SomeFnError(
createUserClose,
addNetworkClose,
getNetworkClose,
setNetworkClose,
+ membershipClose,
+ addMemberClose,
+ addRoleClose,
+ dropRoleClose,
+ editMemberClose,
+ dropMemberClose,
db.Close,
)
@@ -1045,64 +1959,179 @@ func test_setNetworkStmt() {
return user
}
- add := func(user userT) networkT {
+ add := func(user userT) (networkT, memberT) {
newNetwork := newNetworkT{
- uuid: guuid.New(),
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
}
+ memberID := guuid.New()
- network, err := addNetwork(user, newNetwork)
+ network, err := addNetwork(user, newNetwork, memberID)
g.TErrorIf(err)
- return network
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return network, member
}
+ networkChanges := makeNetworkChanges(db, prefix)
- g.Testing("a network needs to exist to be updated", func() {
+
+ g.Testing("creator can change the network", func() {
creator := create()
- virtualNetwork := networkT{
- id: 1234,
- }
+ network1, member := add(creator)
- err := setNetwork(creator, virtualNetwork)
- g.TAssertEqual(err, sql.ErrNoRows)
- })
+ name := network1.name + "name suffix"
+ network1.name = name
+ err := setNetwork(member, network1)
+ g.TErrorIf(err)
- g.Testing("creator can change the network", func() {
- // FIXME
+ network2, err := getNetwork(creator, network1.uuid)
+ g.TErrorIf(err)
+ g.TAssertEqual(network2.name, name)
})
- g.Testing(`"network-settings-admin" can change the network`, func() {
- // FIXME
+ g.Testing(`"network-settings-update" can change the network`, func() {
+ creator := create()
+ admin := create()
+ network, creatorMember := add(creator)
+ newMember := newMemberT{
+ userID: admin.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ adminMember, err := addMember(creatorMember, newMember)
+ g.TErrorIf(err)
+
+ err = addRole(
+ creatorMember,
+ "network-settings-update",
+ adminMember,
+ )
+ g.TErrorIf(err)
+
+ name := network.name + "name suffix"
+ network.name = name
+ err = setNetwork(adminMember, network)
+ g.TErrorIf(err)
})
- g.Testing("ex-admin creator looses ability to change it", func() {
- // FIXME
+ g.Testing(`"admin" can change the network`, func() {
+ creator := create()
+ admin := create()
+ network, creatorMember := add(creator)
+ newMember := newMemberT{
+ userID: admin.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ adminMember, err := addMember(creatorMember, newMember)
+ g.TErrorIf(err)
+
+ err = addRole(creatorMember, "admin", adminMember)
+ g.TErrorIf(err)
+
+ name := network.name + "name suffix"
+ network.name = name
+ err = setNetwork(adminMember, network)
+ g.TErrorIf(err)
})
g.Testing("ex-member creator looses ability to change it", func() {
- // FIXME
+ creator := create()
+ network, member := add(creator)
+
+ err := dropMember(member, member.uuid)
+ g.TErrorIf(err)
+
+ network.name = network.name + "name suffix"
+ err = setNetwork(member, network)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintTrigger,
+ )
+ })
+
+ g.Testing("ex-admin member looses ability to change it", func() {
+ creator := create()
+ admin := create()
+ network, creatorMember := add(creator)
+ newMember := newMemberT{
+ userID: admin.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ adminMember, err := addMember(creatorMember, newMember)
+ g.TErrorIf(err)
+
+ err = addRole(creatorMember, "admin", adminMember)
+ g.TErrorIf(err)
+
+ name := network.name + "name suffix"
+ network.name = name
+ err = setNetwork(adminMember, network)
+ g.TErrorIf(err)
+
+ err = dropRole(creatorMember, "admin", adminMember)
+ g.TErrorIf(err)
+
+ err = setNetwork(adminMember, network)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintTrigger,
+ )
})
g.Testing("unauthorized users can't change the network", func() {
creator := create()
- member := create()
- network := add(creator)
+ user := create()
+ network, creatorMember := add(creator)
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ userMember, err := addMember(creatorMember, newMember)
+ g.TErrorIf(err)
network.name = "member can't set the name"
- err := setNetwork(member, network)
- g.TAssertEqual(err, "403")
+ err = setNetwork(userMember, network)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintTrigger,
+ )
+ })
+
+ g.Testing("non members can't change the network", func() {
+ creator := create()
+ network, member := add(creator)
+ _, otherMember := add(creator)
+
+ network.name = "non member can't set the name xablauzinho"
+ err1 := setNetwork(otherMember, network)
+ err2 := setNetwork(member, network)
+ g.TAssertEqual(
+ err1.(golite.Error).ExtendedCode,
+ golite.ErrConstraintTrigger,
+ )
+ g.TErrorIf(err2)
})
g.Testing("after setting, getting gives us the newer data", func() {
- creator := create()
- network1 := add(creator)
+ creator := create()
+ network1, member := add(creator)
network2 := network1
network2.name = "first network name"
network2.description = "first network description"
network2.type_ = NetworkType_Private
- err := setNetwork(creator, network2)
+ err := setNetwork(member, network2)
g.TErrorIf(err)
network3, err := getNetwork(creator, network1.uuid)
@@ -1112,19 +2141,136 @@ func test_setNetworkStmt() {
g.Testing("the uuid, timestamp or creator never changes", func() {
creator := create()
- network1 := add(creator)
+ network1, member := add(creator)
network2 := network1
network2.uuid = guuid.New()
network2.timestamp = time.Time{}
- network2.createdBy = guuid.New()
- err := setNetwork(creator, network2)
+ err := setNetwork(member, network2)
g.TErrorIf(err)
network3, err := getNetwork(creator, network1.uuid)
g.TErrorIf(err)
g.TAssertEqual(network3, network1)
+ g.TAssertEqual(reflect.DeepEqual(network3, network2), false)
+ })
+
+ g.Testing("inactive member can't set the network", func() {
+ network, member := add(create())
+
+ member.status = "inactive"
+ err := editMember(member, member)
+ g.TErrorIf(err)
+
+ err = setNetwork(member, network)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintTrigger,
+ )
+ })
+
+ g.Testing("removed member can't set the network", func() {
+ network, member := add(create())
+
+ member.status = "removed"
+ err := editMember(member, member)
+ g.TErrorIf(err)
+
+ err = setNetwork(member, network)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintTrigger,
+ )
+ })
+
+ g.Testing("no extra writes to changes table when no updates", func() {
+ network, member := add(create())
+
+ lenBefore := len(networkChanges(network.uuid))
+
+ err := setNetwork(member, network)
+ g.TErrorIf(err)
+
+ lenAfter := len(networkChanges(network.uuid))
+
+ g.TAssertEqual(lenBefore, lenAfter)
+ })
+
+ g.Testing("updates do cause writes to changes table", func() {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ name: "first name",
+ description: "first description",
+ type_: NetworkType_Public,
+ }
+ memberID := guuid.New()
+
+ network, err := addNetwork(creator, newNetwork, memberID)
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ lenBefore := len(networkChanges(network.uuid))
+
+ network.name = "second name"
+ network.description = "second description"
+ network.type_ = NetworkType_Unlisted
+ err = setNetwork(member, network)
+ g.TErrorIf(err)
+
+ changes := networkChanges(network.uuid)[lenBefore:]
+ g.TAssertEqual(len(changes), 6)
+ g.TAssertEqual(
+ []string{
+ changes[0].attribute,
+ changes[1].attribute,
+ changes[2].attribute,
+ changes[3].attribute,
+ changes[4].attribute,
+ changes[5].attribute,
+ changes[0].value,
+ changes[1].value,
+ changes[2].value,
+ changes[3].value,
+ changes[4].value,
+ changes[5].value,
+ },
+ []string{
+ "type",
+ "type",
+ "description",
+ "description",
+ "name",
+ "name",
+ "public",
+ "unlisted",
+ "first description",
+ "second description",
+ "first name",
+ "second name",
+ },
+ )
+ g.TAssertEqual(
+ []bool{
+ changes[0].op,
+ changes[1].op,
+ changes[2].op,
+ changes[3].op,
+ changes[4].op,
+ changes[5].op,
+ },
+ []bool{
+ false,
+ true,
+ false,
+ true,
+ false,
+ true,
+ },
+ )
})
g.Testing("no error if closed more than once", func() {
@@ -1137,40 +2283,693 @@ func test_setNetworkStmt() {
}
func test_nipNetworkStmt() {
- // FIXME
+ g.TestStart("nipNetworkStmt()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ getNetwork, getNetworkClose, getNetworkErr := getNetworkStmt(cfg)
+ networks, networksClose, networksErr := networksStmt(cfg)
+ setNetwork, setNetworkClose, setNetworkErr := setNetworkStmt(cfg)
+ nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ addRole, addRoleClose, addRoleErr := addRoleStmt(cfg)
+ editMember, editMemberClose, editMemberErr := editMemberStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ getNetworkErr,
+ networksErr,
+ setNetworkErr,
+ nipNetworkErr,
+ membershipErr,
+ addMemberErr,
+ addRoleErr,
+ editMemberErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ getNetworkClose,
+ networksClose,
+ setNetworkClose,
+ nipNetworkClose,
+ membershipClose,
+ addMemberClose,
+ addRoleClose,
+ editMemberClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func(user userT) (networkT, memberT) {
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return network, member
+ }
+
+ allNetworks := func(user userT) []networkT {
+ rows, err := networks(user)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ networkList := []networkT{}
+ err = networkEach(rows, func(network networkT) error {
+ networkList = append(networkList, network)
+ return nil
+ })
+ g.TErrorIf(err)
+
+ return networkList
+ }
+
+ networkChanges := makeNetworkChanges(db, prefix)
+
+
+ g.Testing("can't `get` a deleted network", func() {
+ creator := create()
+ network, member := add(creator)
+ err := nipNetwork(member)
+ g.TErrorIf(err)
+
+ _, err = getNetwork(creator, network.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("after deletion it vanishes from `networks()`", func() {
+ creator := create()
+ _, member := add(creator)
+
+ g.TAssertEqual(len(allNetworks(creator)), 1)
+
+ err := nipNetwork(member)
+ g.TErrorIf(err)
+
+ g.TAssertEqual(len(allNetworks(creator)), 0)
+ })
+
+ g.Testing("can't `set` a deleted network", func() {
+ network, member := add(create())
+ err := nipNetwork(member)
+ g.TErrorIf(err)
+
+ err = setNetwork(member, network)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("can't delete a network more than once", func() {
+ _, member := add(create())
+
+ err1 := nipNetwork(member)
+ err2 := nipNetwork(member)
+ g.TErrorIf(err1)
+ g.TAssertEqual(err2, sql.ErrNoRows)
+ })
+
+ g.Testing("can't get membership of a delete network", func() {
+ creator := create()
+ network, member := add(creator)
+
+ _, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ err = nipNetwork(member)
+ g.TErrorIf(err)
+
+ _, err = membership(creator, network)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("an admin can delete a network", func() {
+ admin := create()
+ _, creatorMember := add(create())
+ newMember := newMemberT{
+ userID: admin.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ adminMember, err := addMember(creatorMember, newMember)
+ g.TErrorIf(err)
+
+ err = addRole(creatorMember, "admin", adminMember)
+ g.TErrorIf(err)
+
+ err = nipNetwork(adminMember)
+ g.TErrorIf(err)
+ })
+
+ g.Testing("a member can't delete", func() {
+ user := create()
+ _, creatorMember := add(create())
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ userMember, err := addMember(creatorMember, newMember)
+ g.TErrorIf(err)
+
+ err = nipNetwork(userMember)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintTrigger,
+ )
+ })
+
+ g.Testing("an inactive admin member also can't", func() {
+ user := create()
+ _, member := add(create())
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member.status = "inactive"
+ err := editMember(member, member)
+ g.TErrorIf(err)
+
+ _, err = addMember(member, newMember)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintNotNull,
+ )
+ })
+
+ g.Testing("deletion triggers writes to the changes table", func() {
+ network, member := add(create())
+
+ changes1Len := len(networkChanges(network.uuid))
+
+ err := nipNetwork(member)
+ g.TErrorIf(err)
+
+ changes := networkChanges(network.uuid)
+ g.TAssertEqual(len(changes), changes1Len + 2)
+
+ changes = changes[changes1Len:]
+ g.TAssertEqual(
+ []string{
+ changes[0].attribute,
+ changes[1].attribute,
+ changes[0].value,
+ changes[1].value,
+ },
+ []string{
+ "deleted",
+ "deleted",
+ "0",
+ "1",
+ },
+ )
+ g.TAssertEqual(
+ []bool{
+ changes[0].op,
+ changes[1].op,
+ },
+ []bool{
+ false,
+ true,
+ },
+ )
+ })
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ nipNetworkClose(),
+ nipNetworkClose(),
+ nipNetworkClose(),
+ ))
+ })
+}
+
+func test_membershipStmt() {
+ g.TestStart("membershipStmt")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ dropMember, dropMemberClose, dropMemberErr := dropMemberStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ deleteUserErr,
+ addNetworkErr,
+ nipNetworkErr,
+ membershipErr,
+ addMemberErr,
+ dropMemberErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ deleteUserClose,
+ addNetworkClose,
+ nipNetworkClose,
+ membershipClose,
+ addMemberClose,
+ dropMemberClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func(user userT) networkT {
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Private,
+ }
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ return network
+ }
+
+
+ g.Testing("user needs to exist", func() {
+ virtualUser := userT{
+ id: 1234,
+ }
+ network := add(create())
+
+ _, err := membership(virtualUser, network)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("network needs to exist", func() {
+ virtualNetwork := networkT{
+ id: 1234,
+ }
+
+ _, err := membership(create(), virtualNetwork)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("the member contains all of its roles", func() {
+ creator := create()
+ network := add(creator)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+ g.TAssertEqual(member.roles, []string{ "admin", "creator" })
+ })
+
+ g.Testing("a deleted user can't get their membership", func() {
+ creator := create()
+ network := add(creator)
+
+ err := deleteUser(creator.uuid)
+ g.TErrorIf(err)
+
+ _, err = membership(creator, network)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("can't get member from a deleted network", func() {
+ creator := create()
+ network := add(creator)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ err = nipNetwork(member)
+ g.TErrorIf(err)
+
+ _, err = membership(creator, network)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("we get the same data as `addMember()`", func() {
+ creator := create()
+ user := create()
+ network := add(creator)
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: "a username",
+ }
+
+ creatorMember, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ member1, err := addMember(creatorMember, newMember)
+ g.TErrorIf(err)
+
+ member2, err := membership(user, network)
+ g.TErrorIf(err)
+
+ g.TAssertEqual(member2, member1)
+ })
+
+ g.Testing("can't get membership of ex-member", func() {
+ creator := create()
+ admin := create()
+ network := add(creator)
+ newMember := newMemberT{
+ userID: admin.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ creatorMember, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ adminMember, err := addMember(creatorMember, newMember)
+ g.TErrorIf(err)
+
+ err = dropMember(adminMember, adminMember.uuid)
+ g.TErrorIf(err)
+
+ _, err = membership(admin, network)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("a non-member gets an error", func() {
+ network := add(create())
+
+ _, err := membership(create(), network)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("no error if closed more than once", func() {
+ g.TErrorIf(g.SomeError(
+ membershipClose(),
+ membershipClose(),
+ membershipClose(),
))
})
}
func test_addMemberStmt() {
- /*
- FIXME
- g.TestStart("addMember()")
+ g.TestStart("addMemberStmt()")
const (
dbpath = golite.InMemory
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg)
addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ addRole, addRoleClose, addRoleErr := addRoleStmt(cfg)
g.TErrorIf(g.SomeError(
+ createUserErr,
+ deleteUserErr,
addNetworkErr,
+ nipNetworkErr,
+ membershipErr,
addMemberErr,
+ addRoleErr,
))
defer g.SomeFnError(
+ createUserClose,
+ deleteUserClose,
addNetworkClose,
+ nipNetworkClose,
+ membershipClose,
addMemberClose,
+ addRoleClose,
+ db.Close,
)
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func(user userT) (networkT, memberT) {
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return network, member
+ }
+
+
+ g.Testing("the user needs to exist", func() {
+ _, member := add(create())
+ newMember := newMemberT{
+ userID: guuid.New(),
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ _, err := addMember(member, newMember)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintNotNull,
+ )
+ })
+
+ g.Testing(`the member with role "add-member" is allowed`, func() {
+ user1 := create()
+ user2 := create()
+ _, member0 := add(create())
+ newMember1 := newMemberT{
+ userID: user1.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+ newMember2 := newMemberT{
+ userID: user2.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member1, err := addMember(member0, newMember1)
+ g.TErrorIf(err)
+
+ err = addRole(member0, "add-member", member1)
+ g.TErrorIf(err)
+
+ _, err = addMember(member1, newMember2)
+ g.TErrorIf(err)
+ })
+
+ g.Testing(`the member with role "admin" is allowed`, func() {
+ user1 := create()
+ user2 := create()
+ _, member0 := add(create())
+ newMember1 := newMemberT{
+ userID: user1.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+ newMember2 := newMemberT{
+ userID: user2.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member1, err := addMember(member0, newMember1)
+ g.TErrorIf(err)
+
+ err = addRole(member0, "admin", member1)
+ g.TErrorIf(err)
+
+ _, err = addMember(member1, newMember2)
+ g.TErrorIf(err)
+ })
+
+ g.Testing(`member without role "add-member" is forbidden`, func() {
+ user1 := create()
+ user2 := create()
+ _, member0 := add(create())
+ newMember1 := newMemberT{
+ userID: user1.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+ newMember2 := newMemberT{
+ userID: user2.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member1, err := addMember(member0, newMember1)
+ g.TErrorIf(err)
+
+ _, err = addMember(member1, newMember2)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintTrigger,
+ )
+ })
+
+ g.Testing("can't add the same user twice", func() {
+ creator := create()
+ user := create()
+ _, member := add(creator)
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ _, err1 := addMember(member, newMember)
+ _, err2 := addMember(member, newMember)
+ g.TErrorIf(err1)
+ g.TAssertEqual(
+ err2.(golite.Error).ExtendedCode,
+ golite.ErrConstraintUnique,
+ )
+ })
+
+ g.Testing("the memberID must be unique", func() {
+ creator := create()
+ user1 := create()
+ user2 := create()
+ _, member := add(creator)
+ memberID := guuid.New()
+ newMember1 := newMemberT{
+ userID: user1.uuid,
+ memberID: memberID,
+ username: mkstring(),
+ }
+ newMember2 := newMemberT{
+ userID: user2.uuid,
+ memberID: memberID,
+ username: mkstring(),
+ }
+
+ _, err1 := addMember(member, newMember1)
+ _, err2 := addMember(member, newMember2)
+ g.TErrorIf(err1)
+ g.TAssertEqual(
+ err2.(golite.Error).ExtendedCode,
+ golite.ErrConstraintUnique,
+ )
+ })
+
+ g.Testing("the new member can't be a deleted user", func() {
+ creator := create()
+ user := create()
+ _, member := add(creator)
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ err := deleteUser(user.uuid)
+ g.TErrorIf(err)
+
+ _, err = addMember(member, newMember)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintNotNull,
+ )
+ })
+
+ g.Testing("can't add to a deleted network", func() {
+ creator := create()
+ user := create()
+ _, member := add(creator)
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ err := nipNetwork(member)
+ g.TErrorIf(err)
+
+ _, err = addMember(member, newMember)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintNotNull,
+ )
+ })
+
+ g.Testing("the same user can be a member of distinct networks", func() {
+ // FIXME
+ })
+
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
addMemberClose(),
@@ -1178,39 +2977,620 @@ func test_addMemberStmt() {
addMemberClose(),
))
})
- */
}
-func test_showMemberStmt() {
+func test_addRoleStmt() {
+ g.TestStart("addRoleStmt")
+
// FIXME
+}
+
+func test_dropRoleStmt() {
+ g.TestStart("dropRoleStmt")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ dropRole, dropRoleClose, dropRoleErr := dropRoleStmt(cfg)
+ showMember, showMemberClose, showMemberErr := showMemberStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ dropRoleErr,
+ showMemberErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ dropRoleClose,
+ showMemberClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func(user userT) (networkT, memberT) {
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return network, member
+ }
+
+
+ g.Testing("acting member must exist", func() {
+ _, member := add(create())
+ virtualMember := memberT{}
+
+ err := dropRole(virtualMember, "a-role", member)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("target member must also exist", func() {
+ _, member := add(create())
+ virtualMember := memberT{}
+
+ err := dropRole(member, "a-role", virtualMember)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("noop if member doesn't have the role", func() {
+ return // FIXME
+ _, member := add(create())
+ g.TAssertEqual(member.roles, []string{ "admin", "creator" })
+
+ err := dropRole(member, "does-not-exist", member)
+ g.TErrorIf(err)
+
+ member, err = showMember(member, member.uuid)
+ g.TErrorIf(err)
+ g.TAssertEqual(member.roles, []string{ "admin", "creator" })
+ })
+
+ g.Testing("role is removed when exists", func() {
+ _, member := add(create())
+ g.TAssertEqual(member.roles, []string{ "admin", "creator" })
+
+ err := dropRole(member, "admin", member)
+ g.TErrorIf(err)
+
+ member, err = showMember(member, member.uuid)
+ g.TErrorIf(err)
+ g.TAssertEqual(member.roles, []string{ "creator" })
+ })
+
+ g.Testing("member can remove all roles from themselves", func() {
+ // FIXME
+ })
+
+ g.Testing(`member with "role-write" can drop others roles`, func() {
+ // FIXME
+ })
+
+ g.Testing(`member without "role-write" can't`, func() {
+ // FIXME
+ })
+
+ g.Testing("does not affect other members from other networks", func() {
+ // FIXME
+ })
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ dropRoleClose(),
+ dropRoleClose(),
+ dropRoleClose(),
+ ))
+ })
+}
+
+func test_showMemberStmt() {
+ g.TestStart("showMemberStmt()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ showMember, showMemberClose, showMemberErr := showMemberStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ showMemberErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ showMemberClose,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func(user userT) (networkT, memberT) {
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return network, member
+ }
+
+
+ g.Testing("target member must exist", func() {
+ _, member := add(create())
+ _, err := showMember(member, guuid.New())
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("acting member must exist", func() {
+ virtualMember := memberT{
+ id: 1234,
+ }
+
+ _, member := add(create())
+ _, err := showMember(virtualMember, member.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("target user must belong to the same network", func() {
+ _, member1 := add(create())
+ _, member2 := add(create())
+
+ _, err := showMember(member1, member2.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("we get the full member", func() {
+ _, member1 := add(create())
+ member2, err := showMember(member1, member1.uuid)
+ g.TErrorIf(err)
+
+ g.TAssertEqual(member2, member1)
+ })
+
+ g.Testing("no error if closed more than once", func() {
+ g.TErrorIf(g.SomeError(
+ showMemberClose(),
+ showMemberClose(),
+ showMemberClose(),
))
})
}
func test_memberEach() {
- // FIXME
+ g.TestStart("memberEach()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ members, membersClose, membersErr := membersStmt(cfg)
+ editMember, editMemberClose, editMemberErr := editMemberStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ addMemberErr,
+ membersErr,
+ editMemberErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ addMemberClose,
+ membersClose,
+ editMemberClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addM := func(actor memberT, status MemberStatus) memberT {
+ user := create()
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member, err := addMember(actor, newMember)
+ g.TErrorIf(err)
+
+ member.status = status
+ err = editMember(actor, member)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+
+ g.Testing("callback is called once for new network", func() {
+ member := add()
+
+ rows, err := members(member)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ memberIDs := []guuid.UUID{}
+ err = memberEach(rows, func(member memberT) error {
+ memberIDs = append(memberIDs, member.uuid)
+ return nil
+ })
+
+ g.TAssertEqual(len(memberIDs), 1)
+ g.TAssertEqual(memberIDs[0], member.uuid)
+ })
+
+ g.Testing("we halt if the callback returns an error", func() {
+ myErr := errors.New("callback custom error")
+ member := add()
+ expectedIDs := []guuid.UUID{
+ member.uuid,
+ addM(member, MemberStatus_Active).uuid,
+ addM(member, MemberStatus_Active).uuid,
+ addM(member, MemberStatus_Active).uuid,
+ addM(member, MemberStatus_Active).uuid,
+ addM(member, MemberStatus_Active).uuid,
+ }
+
+ rows, err := members(member)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ memberIDs := []guuid.UUID{}
+ err = memberEach(rows, func(member memberT) error {
+ if len(memberIDs) == 3 {
+ return myErr
+ }
+
+ memberIDs = append(memberIDs, member.uuid)
+ return nil
+ })
+ g.TAssertEqual(err, myErr)
+ g.TAssertEqual(len(memberIDs), 3)
+ g.TAssertEqual(memberIDs, expectedIDs[0:3])
+ })
+
+ g.Testing("noop when given nil for *sql.Rows", func() {
+ err := memberEach(nil, func(memberT) error {
+ g.Unreachable()
+ return nil
+ })
+ g.TErrorIf(err)
+ })
}
func test_membersStmt() {
- // FIXME
+ g.TestStart("membersStmt()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ members, membersClose, membersErr := membersStmt(cfg)
+ editMember, editMemberClose, editMemberErr := editMemberStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ nipNetworkErr,
+ membershipErr,
+ addMemberErr,
+ membersErr,
+ editMemberErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ nipNetworkClose,
+ membershipClose,
+ addMemberClose,
+ membersClose,
+ editMemberClose,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ member.roles = nil
+
+ return member
+ }
+
+ addM := func(actor memberT, status MemberStatus) memberT {
+ user := create()
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member, err := addMember(actor, newMember)
+ g.TErrorIf(err)
+
+ member.status = status
+ err = editMember(actor, member)
+ g.TErrorIf(err)
+
+ member.roles = nil
+
+ return member
+ }
+
+ allMembers := func(member memberT) []memberT {
+ rows, err := members(member)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ memberList := []memberT{}
+ err = memberEach(rows, func(member memberT) error {
+ memberList = append(memberList, member)
+ return nil
+ })
+ g.TErrorIf(err)
+
+ return memberList
+ }
+
+
+ // FIXME: members from other networks do not show up
+ g.Testing("inactive and removed members aren't listed", func() {
+ member := add()
+ expected := []memberT{
+ member,
+ addM(member, MemberStatus_Active),
+ addM(member, MemberStatus_Inactive),
+ addM(member, MemberStatus_Inactive),
+ addM(member, MemberStatus_Removed),
+ addM(member, MemberStatus_Removed),
+ }
+
+ given := allMembers(member)
+
+ g.TAssertEqual(len(given), 2)
+ g.TAssertEqual(given[0], expected[0])
+ g.TAssertEqual(given, expected[0:2])
+ })
+
+ g.Testing("a deleted network has 0 members", func() {
+ member := add()
+
+ g.TAssertEqual(len(allMembers(member)), 1)
+
+ err = nipNetwork(member)
+ g.TErrorIf(err)
+
+ g.TAssertEqual(len(allMembers(member)), 0)
+ })
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ membersClose(),
+ membersClose(),
+ membersClose(),
))
})
}
func test_editMemberStmt() {
+ g.TestStart("editMemberStmt()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ nipNetwork, nipNetworkClose, nipNetworkErr := nipNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ showMember, showMemberClose, showMemberErr := showMemberStmt(cfg)
+ editMember, editMemberClose, editMemberErr := editMemberStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ nipNetworkErr,
+ membershipErr,
+ showMemberErr,
+ editMemberErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ nipNetworkClose,
+ membershipClose,
+ showMemberClose,
+ editMemberClose,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ user := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ // FIXME
+ // memberChanges := makeMemberChanges(db, prefix)
+
// FIXME
+ if nipNetwork != nil && showMember != nil && editMember != nil {}
+ if add != nil {}
+
+ g.Testing("edit triggers writes to changes table", func() {
+ // FIXME
+ })
+
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ editMemberClose(),
+ editMemberClose(),
+ editMemberClose(),
))
})
}
@@ -1226,8 +3606,6 @@ func test_dropMemberStmt() {
}
func test_addChannelStmt() {
- // FIXME
- return
g.TestStart("addChannelStmt()")
const (
@@ -1235,7 +3613,7 @@ func test_addChannelStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -1244,65 +3622,649 @@ func test_addChannelStmt() {
dbpath: dbpath,
prefix: prefix,
}
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
- channels, channelsClose, channelsErr := channelsStmt(cfg)
g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
addChannelErr,
- channelsErr,
))
defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
addChannelClose,
- channelsClose,
db.Close,
)
- collect := func(workspaceID guuid.UUID) []channelT {
- rows, err := channels(workspaceID)
- g.TErrorIf(err)
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
- collected := []channelT{}
- err = channelEach(rows, func(channel channelT) error {
- collected = append(collected, channel)
- return nil
- })
+ user, err := createUser(newUser)
g.TErrorIf(err)
- return collected
+
+ return user
}
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
- if true {
- g.TAssertEqual(addChannel, collect)
+ return member
}
- // private channels one is not a part of doesn't show up
- // channels only from the same workspace
+
+ channelChanges := makeChannelChanges(db, prefix)
+
+
+ g.Testing("the new channel has the data it was given", func() {
+ member := add()
+ publicName := "a-name"
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: &publicName,
+ label: "a-label",
+ description: "the description",
+ virtual: false,
+ }
+
+ channel, err := addChannel(member, newChannel)
+ g.TErrorIf(err)
+
+ g.TAssertEqual(channel.id == 0, false)
+ g.TAssertEqual(channel.timestamp == time.Time{}, false)
+ g.TAssertEqual(channel.uuid, newChannel.uuid)
+ g.TAssertEqual(*channel.publicName, "a-name")
+ g.TAssertEqual(channel.label, "a-label")
+ g.TAssertEqual(channel.description, "the description")
+ g.TAssertEqual(channel.virtual, false)
+ })
+
+ g.Testing("new channel causes inserts to the changes table", func() {
+ member := add()
+ publicName := "another name"
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: &publicName,
+ label: "the label",
+ description: "the description",
+ virtual: false,
+ }
+
+ channel, err := addChannel(member, newChannel)
+ g.TErrorIf(err)
+
+ changes := channelChanges(channel.id)
+ g.TAssertEqual(len(changes), 4)
+ g.TAssertEqual(
+ []string{
+ changes[0].attribute,
+ changes[1].attribute,
+ changes[2].attribute,
+ changes[3].attribute,
+ changes[0].value,
+ changes[1].value,
+ changes[2].value,
+ changes[3].value,
+ },
+ []string{
+ "public_name",
+ "label",
+ "description",
+ "virtual",
+ "another name",
+ "the label",
+ "the description",
+ "false",
+ },
+ )
+ g.TAssertEqual(
+ []bool{
+ changes[0].op,
+ changes[1].op,
+ changes[2].op,
+ changes[3].op,
+ },
+ []bool{
+ true,
+ true,
+ true,
+ true,
+ },
+ )
+ })
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ addChannelClose(),
+ addChannelClose(),
+ addChannelClose(),
))
})
}
func test_channelEach() {
- // FIXME
+ g.TestStart("channelEach()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
+ channels, channelsClose, channelsErr := channelsStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ addChannelErr,
+ channelsErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ addChannelClose,
+ channelsClose,
+ db.Close,
+ )
+
+ add := func() memberT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addC := func(member memberT) channelT {
+ publicName := mkstring()
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: &publicName,
+ label: mkstring(),
+ description: mkstring(),
+ virtual: true,
+ }
+
+ channel, err := addChannel(member, newChannel)
+ g.TErrorIf(err)
+
+ return channel
+ }
+
+
+ g.Testing("callback is not called on empty set", func() {
+ rows, err := channels(add())
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ err = channelEach(rows, func(channelT) error {
+ g.Unreachable()
+ return nil
+ })
+ g.TErrorIf(err)
+ })
+
+ g.Testing("the callback is called once for each entry", func() {
+ member := add()
+ expected := []channelT{
+ addC(member),
+ addC(member),
+ addC(member),
+ }
+
+ rows, err := channels(member)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ channels := []channelT{}
+ err = channelEach(rows, func(channel channelT) error {
+ channels = append(channels, channel)
+ return nil
+ })
+ g.TErrorIf(err)
+
+ g.TAssertEqual(channels, expected)
+ })
+
+ g.Testing("we halt if the callback returns an error", func() {
+ member := add()
+ myErr := errors.New("callback error early return")
+ addC(member)
+ addC(member)
+ addC(member)
+ addC(member)
+ addC(member)
+
+ rows1, err1 := channels(member)
+ rows2, err2 := channels(member)
+ g.TErrorIf(err1)
+ g.TErrorIf(err2)
+ defer rows1.Close()
+ defer rows2.Close()
+
+ n1 := 0
+ n2 := 0
+
+ err1 = channelEach(rows1, func(channelT) error {
+ n1++
+ if n1 == 3 {
+ return myErr
+ }
+ return nil
+ })
+
+ err2 = channelEach(rows2, func(channelT) error {
+ n2++
+ return nil
+ })
+
+ g.TAssertEqual(err1, myErr)
+ g.TErrorIf(err2)
+ g.TAssertEqual(n1, 3)
+ g.TAssertEqual(n2, 5)
+ })
+
+ g.Testing("noop when given nil for *sql.Rows", func() {
+ err := channelEach(nil, func(channelT) error {
+ g.Unreachable()
+ return nil
+ })
+ g.TErrorIf(err)
+ })
}
func test_channelsStmt() {
- // FIXME
+ g.TestStart("channelsStmt()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
+ channels, channelsClose, channelsErr := channelsStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ addMemberErr,
+ addChannelErr,
+ channelsErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ addMemberClose,
+ addChannelClose,
+ channelsClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ user := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(user, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(user, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addM := func(member memberT) memberT {
+ user := create()
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ addedMember, err := addMember(member, newMember)
+ g.TErrorIf(err)
+
+ return addedMember
+ }
+
+ addC := func(
+ member memberT,
+ publicName *string,
+ virtual bool,
+ ) channelT {
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: publicName,
+ label: mkstring(),
+ description: mkstring(),
+ virtual: virtual,
+ }
+
+ channel, err := addChannel(member, newChannel)
+ g.TErrorIf(err)
+
+ return channel
+ }
+
+ allChannels := func(member memberT) ([]channelT, error) {
+ rows, err := channels(member)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ channelList := []channelT{}
+ err = channelEach(rows, func(channel channelT) error {
+ channelList = append(channelList, channel)
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return channelList, nil
+ }
+
+
+ g.Testing("when there are no channels, we get 0", func() {
+ channels, err := allChannels(add())
+ g.TErrorIf(err)
+ g.TAssertEqual(len(channels), 0)
+ })
+
+ g.Testing("when only private channels, owner gets all", func() {
+ member := add()
+ addC(member, nil, true)
+ addC(member, nil, true)
+ addC(member, nil, false)
+ addC(member, nil, false)
+
+ channels, err := allChannels(member)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(channels), 4)
+ })
+
+ g.Testing("when only private channels, others get none", func() {
+ member1 := add()
+ member2 := addM(member1)
+ addC(member1, nil, true)
+ addC(member1, nil, true)
+ addC(member1, nil, false)
+ addC(member1, nil, false)
+
+ channels1, err1 := allChannels(member1)
+ channels2, err2 := allChannels(member2)
+ g.TErrorIf(err1)
+ g.TErrorIf(err2)
+ g.TAssertEqual(len(channels1), 4)
+ g.TAssertEqual(len(channels2), 0)
+ })
+
+ g.Testing("private channels we are a member of show up", func() {
+ member1 := add()
+ member2 := addM(member1)
+ name1 := "channel-name-1"
+ name2 := "channel-name-2"
+ addC(member1, nil, true)
+ addC(member2, nil, true)
+ addC(member1, nil, false)
+ addC(member2, nil, false)
+ addC(member1, &name1, true)
+ addC(member2, &name2, false)
+
+ channels, err := allChannels(member1)
+ g.TErrorIf(err)
+ g.TAssertEqual(len(channels), 4)
+ })
+
+ g.Testing("we never list channels from other networks", func() {
+ member := add()
+ name1 := "a name 1"
+ name2 := "a name 2"
+ addC(member, nil, true)
+ addC(member, nil, false)
+ addC(member, &name1, true)
+ addC(member, &name2, false)
+
+ channels, err := allChannels(add())
+ g.TErrorIf(err)
+ g.TAssertEqual(len(channels), 0)
+ })
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ channelsClose(),
+ channelsClose(),
+ channelsClose(),
))
})
}
-func test_topicStmt() {
- // FIXME
+func test_setChannelStmt() {
+ g.TestStart("setChannelStmt()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
+ setChannel, setChannelClose, setChannelErr := setChannelStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ addMemberErr,
+ addChannelErr,
+ setChannelErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ addMemberClose,
+ addChannelClose,
+ setChannelClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addM := func(actor memberT) memberT {
+ user := create()
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member, err := addMember(actor, newMember)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addC := func(actor memberT, description string) channelT {
+ publicName := mkstring()
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: &publicName,
+ label: mkstring(),
+ description: description,
+ virtual: false,
+ }
+
+ channel, err := addChannel(actor, newChannel)
+ g.TErrorIf(err)
+
+ return channel
+ }
+
+
+ g.Testing("acting member must exist", func() {
+ channel := addC(add(), "description")
+ virtualMember := memberT{
+ id: 1234,
+ }
+
+ err := setChannel(virtualMember, channel)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("target channel must exist", func() {
+ virtualChannel := channelT{
+ id: 1234,
+ }
+
+ err := setChannel(add(), virtualChannel)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("member can't set topic of other network", func() {
+ member := add()
+ otherMember := add()
+ channel := addC(member, "desc")
+
+ err := setChannel(otherMember, channel)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("actor must participate in the channel to set topic", func() {
+ member1 := add()
+ member2 := addM(member1)
+ channel := addC(member1, "desc")
+
+ err := setChannel(member2, channel)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("participant can edit", func() {
+ member := add()
+ channel := addC(member, "first description")
+
+ channel.description = "second description"
+ err := setChannel(member, channel)
+ g.TErrorIf(err)
+ })
+
+ // we can make a private channel public
+ // we can make a public channel private
+
+ g.Testing("update adds entries to *_changes table", func() {
+ // FIXME
+ })
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ setChannelClose(),
+ setChannelClose(),
+ setChannelClose(),
))
})
}
@@ -1318,21 +4280,371 @@ func test_endChannelStmt() {
}
func test_joinStmt() {
+ g.TestStart("joinStmt()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
+ setChannel, setChannelClose, setChannelErr := setChannelStmt(cfg)
+ join, joinClose, joinErr := joinStmt(cfg)
+ part, partClose, partErr := partStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ addMemberErr,
+ addChannelErr,
+ setChannelErr,
+ joinErr,
+ partErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ addMemberClose,
+ addChannelClose,
+ setChannelClose,
+ joinClose,
+ partClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addM := func(actor memberT) memberT {
+ user := create()
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member, err := addMember(actor, newMember)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addC := func(actor memberT, publicName *string, virtual bool) channelT {
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: publicName,
+ label: mkstring(),
+ description: mkstring(),
+ virtual: virtual,
+ }
+
+ channel, err := addChannel(actor, newChannel)
+ g.TErrorIf(err)
+
+ return channel
+ }
+
+
+ g.Testing("acting member must exist", func() {
+ name := "name"
+ channel := addC(add(), &name, false)
+ virtualMember := memberT{
+ id: 1234,
+ }
+
+ err := join(virtualMember, channel.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("target channel must exist", func() {
+ err := join(add(), guuid.New())
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("can't join a private channel", func() {
+ creator := add()
+ member := addM(creator)
+ channel := addC(creator, nil, false)
+
+ err := join(member, channel.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("can't join a virtual channel", func() {
+ creator := add()
+ member := addM(creator)
+ channel := addC(creator, nil, true)
+
+ err := join(member, channel.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("can't join channel in different network", func() {
+ name := "name"
+ member1 := add()
+ member2 := add()
+ channel := addC(member1, &name, false)
+
+ err := join(member2, channel.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("creator can't rejoin after leaving private channel", func() {
+ creator := add()
+ channel := addC(creator, nil, false)
+
+ err := part(creator, channel)
+ g.TErrorIf(err)
+
+ err = join(creator, channel.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("can't join public channel one already participates", func() {
+ name := "name"
+ creator := add()
+ channel := addC(creator, &name, false)
+
+ err := join(creator, channel.uuid)
+ g.TAssertEqual(
+ err.(golite.Error).ExtendedCode,
+ golite.ErrConstraintUnique,
+ )
+ })
+
+ g.Testing("neither a private channel", func() {
+ creator := add()
+ channel := addC(creator, nil, false)
+
+ err := join(creator, channel.uuid)
+ return // FIXME
+ g.TAssertEqual(
+ err,
+ golite.ErrConstraintUnique,
+ )
+ })
+
+ g.Testing("after made public, one can join a channel", func() {
+ name := "name"
+ creator := add()
+ member := addM(creator)
+ channel := addC(creator, nil, false)
+
+ err := join(member, channel.uuid)
+ g.TAssertEqual(err, sql.ErrNoRows)
+
+ channel.publicName = &name
+ err = setChannel(creator, channel)
+ g.TErrorIf(err)
+
+ err = join(member, channel.uuid)
+ g.TErrorIf(err)
+ })
+
// FIXME
+ // creates "user-join" event in feed
+ // joining adds rows to *_changes table
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ joinClose(),
+ joinClose(),
+ joinClose(),
))
})
}
func test_partStmt() {
+ g.TestStart("partStmt()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addMember, addMemberClose, addMemberErr := addMemberStmt(cfg)
+ addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
+ part, partClose, partErr := partStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ addMemberErr,
+ addChannelErr,
+ partErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ addMemberClose,
+ addChannelClose,
+ partClose,
+ db.Close,
+ )
+
+ create := func() userT{
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addM := func(actor memberT) memberT {
+ user := create()
+ newMember := newMemberT{
+ userID: user.uuid,
+ memberID: guuid.New(),
+ username: mkstring(),
+ }
+
+ member, err := addMember(actor, newMember)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addC := func(actor memberT, virtual bool) channelT {
+ publicName := "public name"
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: &publicName,
+ label: mkstring(),
+ description: mkstring(),
+ virtual: virtual,
+ }
+
+ channel, err := addChannel(actor, newChannel)
+ g.TErrorIf(err)
+
+ return channel
+ }
+
+
+ g.Testing("acting member must exist", func() {
+ channel := addC(add(), false)
+ virtualMember := memberT{
+ id: 1234,
+ }
+
+ err := part(virtualMember, channel)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("target channel must exist", func() {
+ virtualChannel := channelT{
+ id: 1234,
+ }
+
+ err := part(add(), virtualChannel)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("must be a member to part", func() {
+ creator := add()
+ member := addM(creator)
+ channel := addC(creator, false)
+
+ err := part(member, channel)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("can't part from a virtual channel", func() {
+ member := add()
+ channel := addC(member, true)
+
+ err := part(member, channel)
+ g.TAssertEqual(err, sql.ErrNoRows)
+ })
+
+ g.Testing("can part from non-virtual channel", func() {
+ member := add()
+ channel := addC(member, false)
+
+ err := part(member, channel)
+ g.TErrorIf(err)
+ })
+
+
// FIXME
+ // parting adds rows to *_changes table
+ // after parting, vanishes from member channel list
g.Testing("no error if closed more than once", func() {
g.TErrorIf(g.SomeError(
- // FIXME
+ partClose(),
+ partClose(),
+ partClose(),
))
})
}
@@ -1352,7 +4664,6 @@ func test_namesStmt() {
}
func test_addEventStmt() {
- return // FIXME
g.TestStart("addEventStmt()")
const (
@@ -1360,7 +4671,7 @@ func test_addEventStmt() {
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -1369,43 +4680,83 @@ func test_addEventStmt() {
dbpath: dbpath,
prefix: prefix,
}
- addEvent, addEventClose, addEventErr := addEventStmt(cfg)
- g.TErrorIf(addEventErr)
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
+ addEvent, addEventClose, addEventErr := addEventStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ addChannelErr,
+ addEventErr,
+ ))
defer g.SomeFnError(
- addEventClose,
- db.Close,
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ addChannelClose,
+ addEventClose,
+ db.Close,
)
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
- g.Testing("we can create new events", func() {
- newEvent := newEventT{
- eventID: guuid.New(),
- channelID: guuid.New(),
- connectionID: guuid.New(),
- type_: "user-message",
- payload: "xablau",
+ return user
+ }
+
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
}
- _, err := addEvent(newEvent)
+ network, err := addNetwork(creator, newNetwork, guuid.New())
g.TErrorIf(err)
- })
- g.Testing("eventID's must be unique", func() {
- // FIXME
- })
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
- g.Testing("the database fills the generated values", func() {
- const (
- type_ = "user-message"
- payload = "the payload"
- )
- eventID := guuid.New()
+ return member
+ }
+
+ addC := func(actor memberT) channelT {
+ publicName := mkstring()
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: &publicName,
+ label: mkstring(),
+ description: mkstring(),
+ virtual: false,
+ }
+
+ channel, err := addChannel(actor, newChannel)
+ g.TErrorIf(err)
+
+ return channel
+ }
+
+
+ g.Testing("we can create new events", func() {
+ creator := add()
+ channel := addC(creator)
newEvent := newEventT{
- eventID: eventID,
- channelID: guuid.New(),
- connectionID: guuid.New(),
- type_: type_,
- payload: payload,
+ eventID: guuid.New(),
+ channelID: channel.uuid,
+ source: sourceT{
+ uuid: creator.uuid,
+ type_: SourceType_Logon,
+ },
+ type_: EventType_UserMessage,
+ payload: "the-payload",
}
event, err := addEvent(newEvent)
@@ -1413,19 +4764,91 @@ func test_addEventStmt() {
g.TAssertEqual(event.id == 0, false)
g.TAssertEqual(event.timestamp == time.Time{}, false)
- g.TAssertEqual(event.channelID == guuid.UUID{}, false)
- g.TAssertEqual(event.connectionID == guuid.UUID{}, false)
- g.TAssertEqual(event.uuid, eventID)
- g.TAssertEqual(event.type_, type_)
- g.TAssertEqual(event.payload, payload)
+ g.TAssertEqual(event.uuid, newEvent.eventID)
+ g.TAssertEqual(event.channelID, newEvent.channelID)
+ g.TAssertEqual(event.source, newEvent.source)
+ g.TAssertEqual(event.type_, EventType_UserMessage)
+ g.TAssertEqual(event.payload, "the-payload")
+ g.TAssertEqual(event.metadata == nil, true)
})
- g.Testing("multiple messages can have the same connectionID", func() {
- // FIXME
+ g.Testing("eventID's must be unique", func() {
+ creator := add()
+ channel := addC(creator)
+ newEvent := newEventT{
+ eventID: guuid.New(),
+ channelID: channel.uuid,
+ source: sourceT{
+ uuid: creator.uuid,
+ type_: SourceType_Logon,
+ },
+ type_: EventType_UserMessage,
+ payload: "the payload",
+ }
+
+ _, err1 := addEvent(newEvent)
+ _, err2 := addEvent(newEvent)
+ g.TErrorIf(err1)
+ g.TAssertEqual(
+ err2.(golite.Error).ExtendedCode,
+ golite.ErrConstraintUnique,
+ )
})
- g.Testing("messages can be dupicated: same type and payload", func() {
- // FIXME
+ g.Testing("multiple messages can have the same source", func() {
+ source := sourceT{
+ uuid: guuid.New(),
+ type_: SourceType_Logon,
+ }
+
+ newEvent1 := newEventT{
+ eventID: guuid.New(),
+ channelID: addC(add()).uuid,
+ source: source,
+ type_: EventType_UserMessage,
+ }
+ newEvent2 := newEventT{
+ eventID: guuid.New(),
+ channelID: addC(add()).uuid,
+ source: source,
+ type_: EventType_UserMessage,
+ }
+
+ _, err1 := addEvent(newEvent1)
+ _, err2 := addEvent(newEvent2)
+ g.TErrorIf(err1)
+ g.TErrorIf(err2)
+ })
+
+ g.Testing("messages can be duplicated: same type and payload", func() {
+ type_ := EventType_UserMessage
+ payload := "a-payload"
+
+ newEvent1 := newEventT{
+ eventID: guuid.New(),
+ channelID: addC(add()).uuid,
+ source: sourceT{
+ uuid: guuid.New(),
+ type_: SourceType_Logon,
+ },
+ type_: type_,
+ payload: payload,
+ }
+ newEvent2 := newEventT{
+ eventID: guuid.New(),
+ channelID: addC(add()).uuid,
+ source: sourceT{
+ uuid: guuid.New(),
+ type_: SourceType_Logon,
+ },
+ type_: type_,
+ payload: payload,
+ }
+
+ _, err1 := addEvent(newEvent1)
+ _, err2 := addEvent(newEvent2)
+ g.TErrorIf(err1)
+ g.TErrorIf(err2)
})
g.Testing("no error if closed more than once", func() {
@@ -1438,18 +4861,210 @@ func test_addEventStmt() {
}
func test_eventEach() {
- // FIXME
+ g.TestStart("eventEach()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ db, err := sql.Open(golite.DriverName, dbpath)
+ g.TErrorIf(err)
+ g.TErrorIf(createTables(db, prefix))
+
+ cfg := dbconfigT{
+ shared: db,
+ dbpath: dbpath,
+ prefix: prefix,
+ }
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
+ addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
+ addEvent, addEventClose, addEventErr := addEventStmt(cfg)
+ allAfter, allAfterClose, allAfterErr := allAfterStmt(cfg)
+ g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
+ addChannelErr,
+ addEventErr,
+ allAfterErr,
+ ))
+ defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
+ addChannelClose,
+ addEventClose,
+ allAfterClose,
+ db.Close,
+ )
+
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addC := func(actor memberT) channelT {
+ publicName := mkstring()
+ newChannel := newChannelT{
+ uuid: guuid.New(),
+ publicName: &publicName,
+ label: mkstring(),
+ description: mkstring(),
+ virtual: false,
+ }
+
+ channel, err := addChannel(actor, newChannel)
+ g.TErrorIf(err)
+
+ return channel
+ }
+
+ eventCount := 0
+ addE := func(channelID guuid.UUID) eventT {
+ eventCount++
+ newEvent := newEventT{
+ // FIXME: missing eventID?
+ channelID: channelID,
+ source: sourceT{
+ uuid: guuid.New(),
+ type_: SourceType_Logon,
+ },
+ type_: EventType_UserMessage,
+ payload: fmt.Sprintf("event %s", eventCount),
+ }
+
+ event, err := addEvent(newEvent)
+ g.TErrorIf(err)
+
+ return event
+ }
+
+
+ g.Testing("callback is not called when there is no message", func() {
+ eventID := guuid.New()
+ member := add()
+ rows, err := allAfter(member, eventID)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ err = eventEach(rows, func(eventT) error {
+ g.Unreachable()
+ return nil
+ })
+ g.TErrorIf(err)
+ })
+
+ g.Testing("the callback is called once for each entry", func() {
+ return // FIXME
+
+ eventID := guuid.New()
+ member := add()
+ channel := addC(member)
+ expected := []eventT{
+ addE(channel.uuid),
+ addE(channel.uuid),
+ addE(channel.uuid),
+ }
+
+ rows, err := allAfter(member, eventID)
+ g.TErrorIf(err)
+ defer rows.Close()
+
+ events := []eventT{}
+ err = eventEach(rows, func(event eventT) error {
+ events = append(events, event)
+ return nil
+ })
+ g.TErrorIf(err)
+
+ g.TAssertEqual(events, expected)
+ })
+
+ g.Testing("it halts if a callback returns an error", func() {
+ return // FIXME
+
+ eventID := guuid.New()
+ member := add()
+ channel := addC(member)
+ myErr := errors.New("callback error early return")
+ addE(channel.uuid)
+ addE(channel.uuid)
+ addE(channel.uuid)
+ addE(channel.uuid)
+ addE(channel.uuid)
+
+ rows1, err1 := allAfter(member, eventID)
+ rows2, err2 := allAfter(member, eventID)
+ g.TErrorIf(err1)
+ g.TErrorIf(err2)
+ defer rows1.Close()
+ defer rows2.Close()
+
+ n1 := 0
+ n2 := 0
+
+ err1 = eventEach(rows1, func(eventT) error {
+ n1++
+ if n1 == 3 {
+ return myErr
+ }
+ return nil
+ })
+
+ err2 = eventEach(rows2, func(eventT) error {
+ n2++
+ return nil
+ })
+
+ g.TAssertEqual(n1, myErr)
+ g.TErrorIf(err2)
+ g.TAssertEqual(n1, 3)
+ g.TAssertEqual(n2, 5)
+ })
+
+ g.Testing("noop when given a nil for *sql.Rows", func() {
+ err := eventEach(nil, func(eventT) error {
+ g.Unreachable()
+ return nil
+ })
+ g.TErrorIf(err)
+ })
}
func test_allAfterStmt() {
- g.TestStart("allAfter()")
+ g.TestStart("allAfterStmt()")
const (
dbpath = golite.InMemory
prefix = defaultPrefix
)
- db, err := sql.Open(golite.DriverName, golite.InMemory)
+ db, err := sql.Open(golite.DriverName, dbpath)
g.TErrorIf(err)
g.TErrorIf(createTables(db, prefix))
@@ -1458,41 +5073,85 @@ func test_allAfterStmt() {
dbpath: dbpath,
prefix: prefix,
}
+ createUser, createUserClose, createUserErr := createUserStmt(cfg)
+ addNetwork, addNetworkClose, addNetworkErr := addNetworkStmt(cfg)
+ membership, membershipClose, membershipErr := membershipStmt(cfg)
addChannel, addChannelClose, addChannelErr := addChannelStmt(cfg)
addEvent, addEventClose, addEventErr := addEventStmt(cfg)
allAfter, allAfterClose, allAfterErr := allAfterStmt(cfg)
g.TErrorIf(g.SomeError(
+ createUserErr,
+ addNetworkErr,
+ membershipErr,
addChannelErr,
addEventErr,
allAfterErr,
))
defer g.SomeFnError(
+ createUserClose,
+ addNetworkClose,
+ membershipClose,
addChannelClose,
addEventClose,
allAfterClose,
db.Close,
)
- channel := func(publicName string) channelT {
- networkID := guuid.New()
+ create := func() userT {
+ newUser := newUserT{
+ uuid: guuid.New(),
+ }
+
+ user, err := createUser(newUser)
+ g.TErrorIf(err)
+
+ return user
+ }
+
+ add := func() memberT {
+ creator := create()
+ newNetwork := newNetworkT{
+ uuid: guuid.New(),
+ type_: NetworkType_Public,
+ }
+
+ network, err := addNetwork(creator, newNetwork, guuid.New())
+ g.TErrorIf(err)
+
+ member, err := membership(creator, network)
+ g.TErrorIf(err)
+
+ return member
+ }
+
+ addC := func(actor memberT) channelT {
+ publicName := mkstring()
newChannel := newChannelT{
- uuid: guuid.New(),
- publicName: publicName,
+ uuid: guuid.New(),
+ publicName: &publicName,
+ label: mkstring(),
+ description: mkstring(),
+ virtual: false,
}
- channel, err := addChannel(networkID, newChannel)
+ channel, err := addChannel(actor, newChannel)
g.TErrorIf(err)
return channel
}
- add := func(channelID guuid.UUID, type_ string, payload string) eventT {
+ eventCount := 0
+ addE := func(channelID guuid.UUID) eventT {
+ eventCount++
newEvent := newEventT{
- eventID: guuid.New(),
- channelID: channelID,
- connectionID: guuid.New(),
- type_: type_,
- payload: payload,
+ eventID: guuid.New(),
+ channelID: channelID,
+ source: sourceT{
+ uuid: guuid.New(),
+ type_: SourceType_Logon,
+ },
+ type_: EventType_UserMessage,
+ payload: fmt.Sprintf("event %s", eventCount),
}
event, err := addEvent(newEvent)
@@ -1501,9 +5160,11 @@ func test_allAfterStmt() {
return event
}
- all := func(eventID guuid.UUID) []eventT {
- rows, err := allAfter(eventID)
+ // FIXME
+ allEvents := func(eventID guuid.UUID) []eventT {
+ rows, err := allAfter(memberT{}, eventID)
g.TErrorIf(err)
+ defer rows.Close()
events := []eventT{}
err = eventEach(rows, func(event eventT) error {
@@ -1517,24 +5178,65 @@ func test_allAfterStmt() {
g.Testing("after joining the channel, there are no events", func() {
- ch := channel("#ch")
- join := add(ch.uuid, "user-join", "fulano")
+ return // FIXME
+
+ eventID := guuid.New()
+ member := add()
+ channel := addC(member)
expected := []eventT{
- add(ch.uuid, "user-join", "ciclano"),
- add(ch.uuid, "user-join", "beltrano"),
- add(ch.uuid, "user-message", "hi there"),
+ // FIXME: missing "user-join" event
+ addE(channel.uuid),
+ addE(channel.uuid),
+ addE(channel.uuid),
}
- given := all(join.uuid)
-
+ given := allEvents(eventID)
g.TAssertEqual(given, expected)
})
g.Testing("we don't get events from other channels", func() {
+ return // FIXME
+
+ eventID := guuid.New()
+ member := add()
+ channel1 := addC(member)
+ channel2 := addC(member)
+
+ events := []eventT{
+ addE(channel1.uuid),
+ addE(channel1.uuid),
+ addE(channel2.uuid),
+ addE(channel2.uuid),
+ addE(channel1.uuid),
+ addE(channel1.uuid),
+ }
+
+ given := allEvents(eventID)
+ g.TAssertEqual(given, events[2:4])
})
g.Testing("as we change the reference point, the list changes", func() {
+ return // FIXME
+
+ eventID := guuid.New()
+ member := add()
+ channel := addC(member)
+
+ events := []eventT{
+ addE(channel.uuid),
+ addE(channel.uuid),
+ addE(channel.uuid),
+ addE(channel.uuid),
+ addE(channel.uuid),
+ }
+
+ g.TAssertEqual(len(allEvents(eventID)), 5)
+ g.TAssertEqual(len(allEvents(events[0].uuid)), 4)
+ g.TAssertEqual(len(allEvents(events[1].uuid)), 3)
+ g.TAssertEqual(len(allEvents(events[2].uuid)), 2)
+ g.TAssertEqual(len(allEvents(events[3].uuid)), 1)
+ g.TAssertEqual(len(allEvents(events[4].uuid)), 0)
})
g.Testing("no error if closed more than once", func() {
@@ -1544,7 +5246,6 @@ func test_allAfterStmt() {
allAfterClose(),
))
})
- // FIXME
}
func test_logMessageStmt() {
@@ -1560,11 +5261,43 @@ func test_logMessageStmt() {
}
func test_initDB() {
- // FIXME
+ g.TestStart("initDB()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ queries, err := initDB(dbpath, prefix)
+ g.TErrorIf(err)
+ defer queries.close()
+
+
+ g.Testing("we can perform all wrapped operations", func() {
+ // FIXME
+ })
}
func test_queriesTclose() {
- // FIXME
+ g.TestStart("queriesT.close()")
+
+ const (
+ dbpath = golite.InMemory
+ prefix = defaultPrefix
+ )
+
+ queries, err := initDB(dbpath, prefix)
+ g.TErrorIf(err)
+ defer queries.close()
+
+
+ g.Testing("closing more than once does not error", func() {
+ g.TErrorIf(g.SomeError(
+ queries.close(),
+ queries.close(),
+ queries.close(),
+ ))
+ })
}
func test_splitOnCRLF() {
@@ -1681,184 +5414,34 @@ func test_parseMessageParams() {
g.Testing("we can parse the string params", func() {
type tableT struct{
input string
- expected messageParamsT
+ expected []string
}
table := []tableT{
- {
- "",
- messageParamsT{
- middle: []string { },
- trailing: "",
- },
- },
- {
- " ",
- messageParamsT{
- middle: []string { },
- trailing: "",
- },
- },
- {
- " :",
- messageParamsT{
- middle: []string { },
- trailing: "",
- },
- },
- {
- " : ",
- messageParamsT{
- middle: []string { },
- trailing: " ",
- },
- },
- {
- ": ",
- messageParamsT{
- middle: []string { ":" },
- trailing: "",
- },
- },
- {
- ": ",
- messageParamsT{
- middle: []string { ":" },
- trailing: "",
- },
- },
- {
- " : ",
- messageParamsT{
- middle: []string { },
- trailing: " ",
- },
- },
- {
- " :",
- messageParamsT{
- middle: []string { },
- trailing: "",
- },
- },
- {
- " :",
- messageParamsT{
- middle: []string { },
- trailing: "",
- },
- },
- {
- "a",
- messageParamsT{
- middle: []string { "a" },
- trailing: "",
- },
- },
- {
- "ab",
- messageParamsT{
- middle: []string { "ab" },
- trailing: "",
- },
- },
- {
- "a b",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: "",
- },
- },
- {
- "a b c",
- messageParamsT{
- middle: []string { "a", "b", "c" },
- trailing: "",
- },
- },
- {
- "a b:c",
- messageParamsT{
- middle: []string { "a", "b:c" },
- trailing: "",
- },
- },
- {
- "a b:c:",
- messageParamsT{
- middle: []string { "a", "b:c:" },
- trailing: "",
- },
- },
- {
- "a b :c",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: "c",
- },
- },
- {
- "a b :c:",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: "c:",
- },
- },
- {
- "a b :c ",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: "c ",
- },
- },
- {
- "a b : c",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: " c",
- },
- },
- {
- "a b : c ",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: " c ",
- },
- },
- {
- "a b : c :",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: " c :",
- },
- },
- {
- "a b : c : ",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: " c : ",
- },
- },
- {
- "a b : c :d",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: " c :d",
- },
- },
- {
- "a b : c :d ",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: " c :d ",
- },
- },
- {
- "a b : c : d ",
- messageParamsT{
- middle: []string { "a", "b" },
- trailing: " c : d ",
- },
- },
+ { "", []string{} },
+ { " ", []string{} },
+ { " :", []string{ "" } },
+ { " : ", []string{ " " } },
+ { ": ", []string{ ":" } },
+ { ": ", []string{ ":" } },
+ { " : ", []string{ " " } },
+ { " :", []string{ "" } },
+ { " :", []string{ "" } },
+ { "a", []string{ "a" } },
+ { "ab", []string{ "ab" } },
+ { "a b", []string{ "a", "b" } },
+ { "a b c", []string{ "a", "b", "c" } },
+ { "a b:c", []string{ "a", "b:c" } },
+ { "a b:c:", []string{ "a", "b:c:" } },
+ { "a b :c", []string{ "a", "b", "c" } },
+ { "a b :c:", []string{ "a", "b", "c:" } },
+ { "a b :c ", []string{ "a", "b", "c " } },
+ { "a b : c", []string{ "a", "b", " c" } },
+ { "a b : c ", []string{ "a", "b", " c " } },
+ { "a b : c :", []string{ "a", "b", " c :" } },
+ { "a b : c : ", []string{ "a", "b", " c : " } },
+ { "a b : c :d", []string{ "a", "b", " c :d" } },
+ { "a b : c :d ", []string{ "a", "b", " c :d " } },
+ { "a b : c : d ", []string{ "a", "b", " c : d " } },
}
for _, entry := range table {
@@ -1881,10 +5464,7 @@ func test_parseMessage() {
messageT{
prefix: "",
command: "NICK",
- params: messageParamsT{
- middle: []string { "joebloe" },
- trailing: "",
- },
+ params: []string{ "joebloe" },
raw: "NICK joebloe ",
},
}, {
@@ -1892,11 +5472,11 @@ func test_parseMessage() {
messageT{
prefix: "",
command: "USER",
- params: messageParamsT{
- middle: []string {
- "joebloe", "0.0.0.0", "joe",
- },
- trailing: "Joe Bloe",
+ params: []string{
+ "joebloe",
+ "0.0.0.0",
+ "joe",
+ "Joe Bloe",
},
raw: "USER joebloe 0.0.0.0 joe :Joe Bloe",
},
@@ -1905,11 +5485,11 @@ func test_parseMessage() {
messageT{
prefix: "pre",
command: "USER",
- params: messageParamsT{
- middle: []string {
- "joebloe", "0.0.0.0", "joe",
- },
- trailing: "Joe Bloe",
+ params: []string{
+ "joebloe",
+ "0.0.0.0",
+ "joe",
+ "Joe Bloe",
},
raw: ":pre USER joebloe 0.0.0.0 joe :Joe Bloe",
},
@@ -1918,11 +5498,11 @@ func test_parseMessage() {
messageT{
prefix: "pre",
command: "USER",
- params: messageParamsT{
- middle: []string {
- "joebloe", "0.0.0.0", "joe",
- },
- trailing: " Joe Bloe ",
+ params: []string{
+ "joebloe",
+ "0.0.0.0",
+ "joe",
+ " Joe Bloe ",
},
raw: ":pre USER joebloe 0.0.0.0 " +
"joe : Joe Bloe ",
@@ -1932,11 +5512,11 @@ func test_parseMessage() {
messageT{
prefix: "pre",
command: "USER",
- params: messageParamsT{
- middle: []string {
- "jbloe:", "0:0:0:1", "joe::a:",
- },
- trailing: " Joe Bloe ",
+ params: []string{
+ "jbloe:",
+ "0:0:0:1",
+ "joe::a:",
+ " Joe Bloe ",
},
raw: ":pre USER jbloe: 0:0:0:1 " +
"joe::a: : Joe Bloe ",
@@ -1946,10 +5526,7 @@ func test_parseMessage() {
messageT{
prefix: "pre",
command: "USER",
- params: messageParamsT{
- middle: []string { },
- trailing: "Joe Bloe",
- },
+ params: []string{ "Joe Bloe" },
raw: ":pre USER :Joe Bloe",
},
}, {
@@ -1957,10 +5534,7 @@ func test_parseMessage() {
messageT{
prefix: "pre",
command: "USER",
- params: messageParamsT{
- middle: []string { },
- trailing: " Joe Bloe",
- },
+ params: []string{ " Joe Bloe" },
raw: ":pre USER : Joe Bloe",
},
}, {
@@ -1968,10 +5542,7 @@ func test_parseMessage() {
messageT{
prefix: "pre",
command: "USER",
- params: messageParamsT{
- middle: []string { },
- trailing: " Joe Bloe",
- },
+ params: []string{ " Joe Bloe" },
raw: ":pre USER : Joe Bloe",
},
}, {
@@ -1979,10 +5550,7 @@ func test_parseMessage() {
messageT{
prefix: "pre",
command: "USER",
- params: messageParamsT{
- middle: []string { },
- trailing: " ",
- },
+ params: []string{ " " },
raw: ":pre USER : ",
},
}, {
@@ -1990,10 +5558,7 @@ func test_parseMessage() {
messageT{
prefix: "pre",
command: "USER",
- params: messageParamsT{
- middle: []string { },
- trailing: "",
- },
+ params: []string{},
raw: ":pre USER :",
},
}}
@@ -2034,10 +5599,72 @@ func test_parseMessage() {
})
}
+func test_addTrailingSeparator() {
+ g.TestStart("addTrailingSeparator()")
+
+ g.Testing("noop on empty slice", func() {
+ input := []string{}
+ addTrailingSeparator(input)
+ g.TAssertEqual(input, []string{})
+ })
+
+ g.Testing("noop if last doesn't have a space", func() {
+ type tableT struct{
+ input []string
+ expected []string
+ }
+
+ entries := []tableT{
+ { []string{ "" }, []string{ "" } },
+ { []string{ "", "" }, []string{ "", "" } },
+ { []string{ "a", "b" }, []string{ "a", "b" } },
+ {
+ []string{ "a", "b", "c-d" },
+ []string{ "a", "b", "c-d" },
+ },
+ {
+ []string{ "a ", "b", "cd" },
+ []string{ "a ", "b", "cd" },
+ },
+ }
+
+ for i, entry := range entries {
+ addTrailingSeparator(entry.input)
+ g.TAssertEqual(entry.input, entries[i].expected)
+ }
+ })
+
+ g.Testing("add ':' to the last otherwise", func() {
+ type tableT struct{
+ input []string
+ expected []string
+ }
+
+ entries := []tableT{
+ { []string{ " " }, []string{ ": " } },
+ { []string{ "a " }, []string{ ":a " } },
+ { []string{ " a" }, []string{ ": a" } },
+ { []string{ "a ", " b" }, []string{ "a ", ": b" } },
+ { []string{ "a", " b" }, []string{ "a", ": b" } },
+ {
+ []string{ "a", "b", "c d" },
+ []string{ "a", "b", ":c d" },
+ },
+ }
+
+ for i, entry := range entries {
+ addTrailingSeparator(entry.input)
+ addTrailingSeparator(entry.input)
+ g.TAssertEqual(entry.input, entries[i].expected)
+ }
+ })
+}
+
func dumpQueries() {
queries := []struct{name string; fn func(string) queryT}{
{ "createTables", createTablesSQL },
+ { "memberRoles", memberRolesSQL },
{ "createUser", createUserSQL },
{ "userByUUID", userByUUIDSQL },
{ "updateUser", updateUserSQL },
@@ -2047,14 +5674,17 @@ func dumpQueries() {
{ "networks", networksSQL },
{ "setNetwork", setNetworkSQL },
{ "nipNetwork", nipNetworkSQL },
+ { "membership", membershipSQL },
{ "addMember", addMemberSQL },
+ { "addRole", addRoleSQL },
+ { "dropRole", dropRoleSQL },
{ "showMember", showMemberSQL },
{ "members", membersSQL },
{ "editMember", editMemberSQL },
{ "dropMember", dropMemberSQL },
{ "addChannel", addChannelSQL },
{ "channels", channelsSQL },
- { "topic", topicSQL },
+ { "setChannel", setChannelSQL },
{ "endChannel", endChannelSQL },
{ "join", joinSQL },
{ "part", partSQL },
@@ -2081,8 +5711,6 @@ func MainTest() {
g.Init()
test_defaultPrefix()
- test_serialized()
- test_execSerialized()
test_tryRollback()
test_inTx()
test_createTables()
@@ -2096,7 +5724,10 @@ func MainTest() {
test_networksStmt()
test_setNetworkStmt()
test_nipNetworkStmt()
+ test_membershipStmt()
test_addMemberStmt()
+ test_addRoleStmt()
+ test_dropRoleStmt()
test_showMemberStmt()
test_memberEach()
test_membersStmt()
@@ -2105,7 +5736,7 @@ func MainTest() {
test_addChannelStmt()
test_channelEach()
test_channelsStmt()
- test_topicStmt()
+ test_setChannelStmt()
test_endChannelStmt()
test_joinStmt()
test_partStmt()
@@ -2121,4 +5752,5 @@ func MainTest() {
test_splitOnRawMessage()
test_parseMessageParams()
test_parseMessage()
+ test_addTrailingSeparator()
}
diff --git a/tests/queries.sql b/tests/queries.sql
index c996f02..992c8d2 100644
--- a/tests/queries.sql
+++ b/tests/queries.sql
@@ -1,134 +1,271 @@
-- createTables.sql:
-- write:
- -- FIXME: unconfirmed premise: statements within a trigger are
- -- part of the transaction that caused it, and so are
- -- atomic.
+ -- TODO: unconfirmed premise: statements within a trigger are
+ -- part of the transaction that caused it, and so are
+ -- atomic.
-- See also:
-- https://stackoverflow.com/questions/77441888/
-- https://stackoverflow.com/questions/30511116/
CREATE TABLE IF NOT EXISTS "papod_users" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
-- provided by cracha
- uuid BLOB NOT NULL UNIQUE,
+ user_uuid BLOB NOT NULL UNIQUE,
username TEXT NOT NULL,
display_name TEXT NOT NULL,
picture_uuid BLOB UNIQUE,
deleted INT NOT NULL CHECK(deleted IN (0, 1))
) STRICT;
--- CREATE TABLE IF NOT EXISTS "papod_user_changes" (
--- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
--- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
--- user_id INTEGER NOT NULL REFERENCES "papod_users"(id),
--- attribute TEXT NOT NULL CHECK(
--- attribute IN (
--- 'username',
--- 'display_name',
--- 'picture_uuid',
--- 'deleted'
--- )
--- ),
--- value TEXT NOT NULL,
--- op INT NOT NULL CHECK(op IN (0, 1))
--- ) STRICT;
--- CREATE TRIGGER IF NOT EXISTS "papod_user_creation"
--- AFTER INSERT ON "papod_users"
--- BEGIN
--- INSERT INTO "papod_user_changes" (
--- user_id, attribute, value, op
--- ) VALUES
--- (NEW.id, 'username', NEW.username, true),
--- (NEW.id, 'display_name', NEW.display_name, true),
--- (NEW.id, 'deleted', NEW.deleted, true)
--- ;
--- END;
--- CREATE TRIGGER IF NOT EXISTS "papod_user_creation_picture_uuid"
--- AFTER INSERT ON "papod_users"
--- WHEN NEW.picture_uuid != NULL
--- BEGIN
--- INSERT INTO "papod_user_changes" (
--- user_id, attribute, value, op
--- ) VALUES
--- (NEW.id, 'picture_uuid', NEW.picture_uuid, true)
--- ;
--- END;
--- CREATE TRIGGER IF NOT EXISTS "papod_user_update_username"
--- AFTER UPDATE ON "papod_users"
--- WHEN OLD.username != NEW.username
--- BEGIN
--- INSERT INTO "papod_user_changes" (
--- user_id, attribute, value, op
--- ) VALUES
--- (NEW.id, 'username', OLD.username, false),
--- (NEW.id, 'username', NEW.username, true)
--- ;
--- END;
--- CREATE TRIGGER IF NOT EXISTS "papod_user_update_display_name"
--- AFTER UPDATE ON "papod_users"
--- WHEN OLD.display_name != NEW.display_name
--- BEGIN
--- INSERT INTO "papod_user_changes" (
--- user_id, attribute, value, op
--- ) VALUES
--- (NEW.id, 'display_name', OLD.display_name, false),
--- (NEW.id, 'display_name', NEW.display_name, true)
--- ;
--- END;
--- CREATE TRIGGER IF NOT EXISTS "papod_user_update_picture_uuid"
--- AFTER UPDATE ON "papod_users"
--- WHEN OLD.picture_uuid != NEW.picture_uuid
--- BEGIN
--- INSERT INTO "papod_user_changes" (
--- user_id, attribute, value, op
--- ) VALUES
--- (NEW.id, 'picture_uuid', OLD.picture_uuid, false),
--- (NEW.id, 'picture_uuid', NEW.picture_uuid, true)
--- ;
--- END;
--- CREATE TRIGGER IF NOT EXISTS "papod_user_update_deleted"
--- AFTER UPDATE ON "papod_users"
--- WHEN OLD.deleted != NEW.deleted
--- BEGIN
--- INSERT INTO "papod_user_changes" (
--- user_id, attribute, value, op
--- ) VALUES
--- (NEW.id, 'deleted', OLD.deleted, false),
--- (NEW.id, 'deleted', NEW.deleted, true)
--- ;
--- END;
+ CREATE TABLE IF NOT EXISTS "papod_user_changes" (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ user_id INTEGER NOT NULL,
+ attribute TEXT NOT NULL CHECK(
+ attribute IN (
+ 'username',
+ 'display_name',
+ 'picture_uuid',
+ 'deleted'
+ )
+ ),
+ value_text TEXT,
+ value_blob BLOB,
+ value_bool INT CHECK(value_bool IN (0, 1)),
+ op INT NOT NULL CHECK(op IN (0, 1))
+ ) STRICT;
+ CREATE TRIGGER IF NOT EXISTS "papod_user_new"
+ AFTER INSERT ON "papod_users"
+ BEGIN
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'username', NEW.username, true),
+ (NEW.id, 'display_name', NEW.display_name, true)
+ ;
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_bool, op
+ ) VALUES
+ (NEW.id, 'deleted', NEW.deleted, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_user_new_picture_uuid"
+ AFTER INSERT ON "papod_users"
+ WHEN NEW.picture_uuid IS NOT NULL
+ BEGIN
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_blob, op
+ ) VALUES
+ (NEW.id, 'picture_uuid', NEW.picture_uuid, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_user_update_username"
+ AFTER UPDATE ON "papod_users"
+ WHEN OLD.username != NEW.username
+ BEGIN
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'username', OLD.username, false),
+ (NEW.id, 'username', NEW.username, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_user_update_display_name"
+ AFTER UPDATE ON "papod_users"
+ WHEN OLD.display_name != NEW.display_name
+ BEGIN
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'display_name', OLD.display_name, false),
+ (NEW.id, 'display_name', NEW.display_name, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_user_add_picture_uuid"
+ AFTER UPDATE ON "papod_users"
+ WHEN (
+ OLD.picture_uuid IS NULL AND
+ NEW.picture_uuid IS NOT NULL
+ )
+ BEGIN
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_blob, op
+ ) VALUES
+ (NEW.id, 'picture_uuid', NEW.picture_uuid, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_user_remove_picture_uuid"
+ AFTER UPDATE ON "papod_users"
+ WHEN (
+ OLD.picture_uuid IS NOT NULL AND
+ NEW.picture_uuid IS NULL
+ )
+ BEGIN
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_blob, op
+ ) VALUES
+ (NEW.id, 'picture_uuid', OLD.picture_uuid, false)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_user_update_picture_uuid"
+ AFTER UPDATE ON "papod_users"
+ WHEN (
+ OLD.picture_uuid IS NOT NULL AND
+ NEW.picture_uuid IS NOT NULL AND
+ OLD.picture_uuid != NEW.picture_uuid
+ )
+ BEGIN
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_blob, op
+ ) VALUES
+ (NEW.id, 'picture_uuid', OLD.picture_uuid, false),
+ (NEW.id, 'picture_uuid', NEW.picture_uuid, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_user_update_deleted"
+ AFTER UPDATE ON "papod_users"
+ WHEN OLD.deleted != NEW.deleted
+ BEGIN
+ INSERT INTO "papod_user_changes" (
+ user_id, attribute, value_bool, op
+ ) VALUES
+ (NEW.id, 'deleted', OLD.deleted, false),
+ (NEW.id, 'deleted', NEW.deleted, true)
+ ;
+ END;
+
+ CREATE TABLE IF NOT EXISTS "papod_sessions" (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ -- provided by cracha
+ session_uuid BLOB NOT NULL UNIQUE,
+ user_id INTEGER NOT NULL
+ REFERENCES "papod_users"(id),
+ finished_at TEXT
+ );
+ CREATE TABLE IF NOT EXISTS "papod_connections" (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ uuid BLOB NOT NULL UNIQUE,
+ finished_at TEXT
+ );
+ CREATE TABLE IF NOT EXISTS "papod_logons" (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ session_id INTEGER NOT NULL
+ REFERENCES "papod_sessions"(id),
+ connection_id INTEGER NOT NULL
+ REFERENCES "papod_connections"(id),
+ UNIQUE (session_id, connection_id)
+ ) STRICT;
CREATE TABLE IF NOT EXISTS "papod_networks" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
uuid BLOB NOT NULL UNIQUE,
- creator_id INTEGER NOT NULL REFERENCES "papod_users"(id),
name TEXT NOT NULL,
description TEXT NOT NULL,
type TEXT NOT NULL CHECK(
type IN ('public', 'private', 'unlisted')
- )
+ ),
+ deleted INT NOT NULL CHECK(deleted IN (0, 1))
) STRICT;
+ CREATE INDEX IF NOT EXISTS "papod_networks_type"
+ ON "papod_networks"(type);
CREATE TABLE IF NOT EXISTS "papod_network_changes" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
- network_id INTEGER NOT NULL
- REFERENCES "papod_networks"(id),
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ network_id INTEGER NOT NULL,
attribute TEXT NOT NULL CHECK(
attribute IN (
'name',
'description',
- 'type'
+ 'type',
+ 'deleted',
+ 'logon_id' -- FIXME
)
),
value TEXT NOT NULL,
op INT NOT NULL CHECK(op IN (0, 1))
) STRICT;
+ CREATE TRIGGER IF NOT EXISTS "papod_network_new"
+ AFTER INSERT ON "papod_networks"
+ BEGIN
+ INSERT INTO "papod_network_changes" (
+ network_id, attribute, value, op
+ ) VALUES
+ (NEW.id, 'name', NEW.name, true),
+ (NEW.id, 'description', NEW.description, true),
+ (NEW.id, 'type', NEW.type, true),
+ (NEW.id, 'deleted', NEW.deleted, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_network_update_name"
+ AFTER UPDATE ON "papod_networks"
+ WHEN OLD.name != NEW.name
+ BEGIN
+ INSERT INTO "papod_network_changes" (
+ network_id, attribute, value, op
+ ) VALUES
+ (NEW.id, 'name', OLD.name, false),
+ (NEW.id, 'name', NEW.name, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_network_update_description"
+ AFTER UPDATE ON "papod_networks"
+ WHEN OLD.description != NEW.description
+ BEGIN
+ INSERT INTO "papod_network_changes" (
+ network_id, attribute, value, op
+ ) VALUES
+ (NEW.id, 'description', OLD.description, false),
+ (NEW.id, 'description', NEW.description, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_network_update_type"
+ AFTER UPDATE ON "papod_networks"
+ WHEN OLD.description != NEW.description
+ BEGIN
+ INSERT INTO "papod_network_changes" (
+ network_id, attribute, value, op
+ ) VALUES
+ (NEW.id, 'type', OLD.type, false),
+ (NEW.id, 'type', NEW.type, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_network_update_deleted"
+ AFTER UPDATE ON "papod_networks"
+ WHEN OLD.deleted != NEW.deleted
+ BEGIN
+ INSERT INTO "papod_network_changes" (
+ network_id, attribute, value, op
+ ) VALUES
+ (NEW.id, 'deleted', OLD.deleted, false),
+ (NEW.id, 'deleted', NEW.deleted, true)
+ ;
+ END;
CREATE TABLE IF NOT EXISTS "papod_members" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ uuid BLOB NOT NULL UNIQUE,
network_id INTEGER NOT NULL
REFERENCES "papod_networks"(id),
user_id INTEGER NOT NULL,
@@ -144,6 +281,120 @@
UNIQUE (network_id, username, active_uniq),
UNIQUE (network_id, user_id)
) STRICT;
+ CREATE TABLE IF NOT EXISTS "papod_member_changes" (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ member_id INTEGER NOT NULL,
+ attribute TEXT NOT NULL CHECK(
+ attribute IN (
+ 'username',
+ 'display_name',
+ 'picture_uuid',
+ 'status',
+ 'logon_id' -- FIXME
+ )
+ ),
+ value_text TEXT,
+ value_blob BLOB,
+ op INT NOT NULL CHECK(op IN (0, 1))
+ ) STRICT;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_new"
+ AFTER INSERT ON "papod_members"
+ BEGIN
+ INSERT INTO "papod_member_changes" (
+ member_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'username', NEW.username, true),
+ (NEW.id, 'display_name', NEW.display_name, true),
+ (NEW.id, 'status', NEW.status, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_new_picture_uuid"
+ AFTER INSERT ON "papod_members"
+ WHEN NEW.picture_uuid IS NOT NULL
+ BEGIN
+ INSERT INTO "papod_member_changes" (
+ member_id, attribute, value_blob, op
+ ) VALUES
+ (NEW.id, 'picture_uuid', NEW.picture_uuid, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_update_username"
+ AFTER UPDATE ON "papod_members"
+ WHEN OLD.username != NEW.username
+ BEGIN
+ INSERT INTO "papod_member_changes" (
+ member_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'username', OLD.username, false),
+ (NEW.id, 'username', NEW.username, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_update_display_name"
+ AFTER UPDATE ON "papod_members"
+ WHEN OLD.display_name != NEW.display_name
+ BEGIN
+ INSERT INTO "papod_member_changes" (
+ member_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'display_name', OLD.display_name, false),
+ (NEW.id, 'display_name', NEW.display_name, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_update_status"
+ AFTER UPDATE ON "papod_members"
+ WHEN OLD.status != NEW.status
+ BEGIN
+ INSERT INTO "papod_member_changes" (
+ member_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'status', OLD.status, false),
+ (NEW.id, 'status', NEW.status, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_add_picture_uuid"
+ AFTER UPDATE ON "papod_members"
+ WHEN (
+ OLD.picture_uuid IS NULL AND
+ NEW.picture_uuid IS NOT NULL
+ )
+ BEGIN
+ INSERT INTO "papod_member_changes" (
+ member_id, attribute, value_blob, op
+ ) VALUES
+ (NEW.id, 'picture_uuid', NEW.picture_uuid, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_remove_picture_uuid"
+ AFTER UPDATE ON "papod_members"
+ WHEN (
+ OLD.picture_uuid IS NOT NULL AND
+ NEW.picture_uuid IS NULL
+ )
+ BEGIN
+ INSERT INTO "papod_member_changes" (
+ member_id, attribute, value_blob, op
+ ) VALUES
+ (NEW.id, 'picture_uuid', OLD.picture_uuid, false)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_update_picture_uuid"
+ AFTER UPDATE ON "papod_members"
+ WHEN (
+ OLD.picture_uuid IS NOT NULL AND
+ NEW.picture_uuid IS NOT NULL AND
+ OLD.picture_uuid != NEW.picture_uuid
+ )
+ BEGIN
+ INSERT INTO "papod_member_changes" (
+ member_id, attribute, value_blob, op
+ ) VALUES
+ (NEW.id, 'picture_uuid', OLD.picture_uuid, false),
+ (NEW.id, 'picture_uuid', NEW.picture_uuid, true)
+ ;
+ END;
CREATE TABLE IF NOT EXISTS "papod_member_roles" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
@@ -152,40 +403,157 @@
role TEXT NOT NULL,
UNIQUE (member_id, role)
) STRICT;
-
- -- FIXME: use a trigger
- CREATE TABLE IF NOT EXISTS "papod_member_changes" (
+ CREATE TABLE IF NOT EXISTS "papod_member_role_changes" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
- member_id INTEGER NOT NULL
- REFERENCES "papod_members"(id),
- attribute TEXT NOT NULL,
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ role_id INTEGER NOT NULL,
+ attribute TEXT NOT NULL CHECK(
+ attribute IN (
+ 'role',
+ 'logon_id' -- FIXME
+ )
+ ),
value TEXT NOT NULL,
op INT NOT NULL CHECK(op IN (0, 1))
) STRICT;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_role_add"
+ AFTER INSERT ON "papod_member_roles"
+ BEGIN
+ INSERT INTO "papod_member_role_changes" (
+ role_id, attribute, value, op
+ ) VALUES
+ (NEW.id, 'role', NEW.role, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_member_role_remove"
+ AFTER DELETE ON "papod_member_roles"
+ BEGIN
+ INSERT INTO "papod_member_role_changes" (
+ role_id, attribute, value, op
+ ) VALUES
+ (OLD.id, 'role', OLD.role, false)
+ ;
+ END;
CREATE TABLE IF NOT EXISTS "papod_channels" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
uuid BLOB NOT NULL UNIQUE,
- network_id INTEGER -- FIXME NOT NULL
+ network_id INTEGER NOT NULL
REFERENCES "papod_networks"(id),
- public_name TEXT UNIQUE,
+ public_name TEXT,
label TEXT NOT NULL,
description TEXT NOT NULL,
- virtual INT NOT NULL CHECK(virtual IN (0, 1))
+ virtual INT NOT NULL CHECK(virtual IN (0, 1)),
+ UNIQUE (network_id, public_name)
) STRICT;
-
- -- FIXME: use a trigger
CREATE TABLE IF NOT EXISTS "papod_channel_changes" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
- channel_id INTEGER NOT NULL
- REFERENCES "papod_channels"(id),
- attribute TEXT NOT NULL,
- value TEXT NOT NULL,
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ channel_id INTEGER NOT NULL,
+ attribute TEXT NOT NULL CHECK(
+ attribute IN (
+ 'public_name',
+ 'label',
+ 'description',
+ 'virtual',
+ 'logon_id' -- FIXME
+ )
+ ),
+ value_text TEXT,
+ value_bool INT CHECK(value_bool IN (0, 1)),
op INT NOT NULL CHECK(op IN (0, 1))
) STRICT;
+ CREATE TRIGGER IF NOT EXISTS "papod_channel_new"
+ AFTER INSERT ON "papod_channels"
+ BEGIN
+ INSERT INTO "papod_channel_changes" (
+ channel_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'label', NEW.label, true),
+ (NEW.id, 'description', NEW.description, true)
+ ;
+ INSERT INTO "papod_channel_changes" (
+ channel_id, attribute, value_bool, op
+ ) VALUES
+ (NEW.id, 'virtual', NEW.virtual, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_channel_new_public_name"
+ AFTER INSERT ON "papod_channels"
+ WHEN NEW.public_name IS NOT NULL
+ BEGIN
+ INSERT INTO "papod_channel_changes" (
+ channel_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'public_name', NEW.public_name, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_channel_update_label"
+ AFTER UPDATE ON "papod_channels"
+ WHEN OLD.label != NEW.label
+ BEGIN
+ INSERT INTO "papod_channel_changes" (
+ channel_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'label', OLD.label, false),
+ (NEW.id, 'label', NEW.label, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_channel_update_description"
+ AFTER UPDATE ON "papod_channels"
+ WHEN OLD.description != NEW.description
+ BEGIN
+ INSERT INTO "papod_channel_changes" (
+ channel_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'description', OLD.description, false),
+ (NEW.id, 'description', NEW.description, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_channel_update_virtual"
+ AFTER UPDATE ON "papod_channels"
+ WHEN OLD.virtual != NEW.virtual
+ BEGIN
+ INSERT INTO "papod_channel_changes" (
+ channel_id, attribute, value_bool, op
+ ) VALUES
+ (NEW.id, 'virtual', OLD.virtual, false),
+ (NEW.id, 'virtual', NEW.virtual, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_channel_add_public_name"
+ AFTER UPDATE ON "papod_channels"
+ WHEN (
+ OLD.public_name IS NULL AND
+ NEW.public_name IS NOT NULL
+ )
+ BEGIN
+ INSERT INTO "papod_channel_changes" (
+ channel_id, attribute, value_text, op
+ ) VALUES
+ (NEW.id, 'public_name', NEW.public_name, true)
+ ;
+ END;
+ CREATE TRIGGER IF NOT EXISTS "papod_channel_remove_public_name"
+ AFTER UPDATE ON "papod_channels"
+ WHEN (
+ OLD.public_name IS NOT NULL AND
+ NEW.public_name IS NULL
+ )
+ BEGIN
+ INSERT INTO "papod_channel_changes" (
+ channel_id, attribute, value_text, op
+ ) VALUES
+ (OLD.id, 'public_name', OLD.public_name, false)
+ ;
+ END;
CREATE TABLE IF NOT EXISTS "papod_participants" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
@@ -195,37 +563,65 @@
REFERENCES "papod_members"(id),
UNIQUE (channel_id, member_id)
) STRICT;
+ CREATE TABLE IF NOT EXISTS "papod_participant_changes" (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
+ participant_id INTEGER NOT NULL,
+ attribute TEXT NOT NULL CHECK(
+ attribute IN (
+ 'connection_id'
+ )
+ ),
+ value TEXT NOT NULL,
+ op INT NOT NULL CHECK(op IN (0, 1))
+ ) STRICT;
- -- FIXME: create database table for connections?
- -- A user can have multiple sessions (different browsers,
- -- mobile, etc.), and each session has multiple connections, as
- -- the user connects and disconnections using the same session
- -- id, all while it is valid.
- -- FIXME: can a connection have multiple sessions? A long-lived
- -- connection that spans multiple sessions would fit into this.
CREATE TABLE IF NOT EXISTS "papod_channel_events" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')),
+ timestamp TEXT NOT NULL DEFAULT (
+ strftime('%Y-%m-%dT%H:%M:%f000000Z', 'now')
+ ),
uuid BLOB NOT NULL UNIQUE,
channel_id INTEGER NOT NULL
REFERENCES "papod_channels"(id),
- connection_uuid BLOB NOT NULL, -- FIXME: join
+ source_uuid BLOB NOT NULL,
+ source_type TEXT NOT NULL CHECK(
+ source_type IN (
+ 'logon'
+ )
+ ),
+ source_metadata TEXT,
type TEXT NOT NULL CHECK(
type IN (
'user-join',
'user-message'
)
),
- payload TEXT NOT NULL
+ payload TEXT NOT NULL,
+ metadata TEXT
) STRICT;
+
-- read:
+-- memberRoles.sql:
+-- write:
+
+-- read:
+ SELECT role FROM "papod_member_roles"
+ JOIN "papod_members" ON
+ "papod_member_roles".member_id = "papod_members".id
+ WHERE "papod_members".uuid = ?
+ ORDER BY "papod_member_roles".id;
+
+
-- createUser.sql:
-- write:
INSERT INTO "papod_users" (
- uuid, username, display_name, picture_uuid, deleted
+ user_uuid, username, display_name, picture_uuid, deleted
) VALUES (
?, ?, ?, NULL, false
) RETURNING id, timestamp;
@@ -245,8 +641,8 @@
picture_uuid
FROM "papod_users"
WHERE
- uuid = ? AND
- deleted = false;
+ user_uuid = ? AND
+ deleted = false;
-- updateUser.sql:
@@ -269,8 +665,8 @@
UPDATE "papod_users"
SET deleted = true
WHERE
- uuid = ? AND
- deleted = false
+ user_uuid = ? AND
+ deleted = false
RETURNING id;
@@ -279,92 +675,267 @@
-- addNetwork.sql:
-- write:
INSERT INTO "papod_networks" (
- uuid, name, description, type, creator_id
+ uuid, name, description, type, deleted
)
VALUES (
?,
?,
?,
?,
- (
- SELECT id FROM "papod_users"
- WHERE id = ? AND deleted = false
- )
- ) RETURNING id, timestamp;
+ false
+ ) RETURNING id;
+ WITH creator AS (
+ SELECT username, display_name, picture_uuid
+ FROM "papod_users"
+ WHERE id = ? AND deleted = false
+ ), new_network AS (
+ SELECT id FROM "papod_networks" WHERE uuid = ?
+ )
INSERT INTO "papod_members" (
- network_id, user_id, username, display_name,
+ uuid, network_id, user_id, username, display_name,
picture_uuid, status, active_uniq
) VALUES (
- last_insert_rowid(),
?,
- (
- SELECT username, display_name, picture_uuid
- FROM "papod_users"
- WHERE id = ? AND deleted = false
- ),
+ (SELECT id FROM new_network),
+ ?,
+ (SELECT username FROM creator),
+ (SELECT display_name FROM creator),
+ (SELECT picture_uuid FROM creator),
'active',
'active'
- ) RETURNING id, timestamp;
+ ) RETURNING id;
+
+ WITH new_member AS (
+ SELECT id FROM "papod_members" WHERE uuid = ?
+ )
+ INSERT INTO "papod_member_roles" (member_id, role)
+ VALUES (
+ (SELECT id FROM new_member),
+ 'admin'
+ ),
+ (
+ (SELECT id FROM new_member),
+ 'creator'
+ )
+ RETURNING id;
-- read:
+ SELECT id, timestamp FROM "papod_networks"
+ WHERE uuid = ? AND deleted = false;
+
-- getNetwork.sql:
-- write:
-- read:
+ WITH probing_user AS (
+ SELECT id FROM "papod_users"
+ WHERE id = ? AND deleted = false
+ ), target_network AS (
+ SELECT id FROM "papod_networks"
+ WHERE uuid = ? AND deleted = false
+ )
SELECT
- "papod_networks".id,
- "papod_networks".timestamp,
- "papod_users".uuid,
- "papod_networks".name,
- "papod_networks".description,
- "papod_networks".type
+ id,
+ timestamp,
+ name,
+ description,
+ type
FROM "papod_networks"
- JOIN "papod_users" ON
- "papod_users".id = "papod_networks".creator_id
WHERE
- "papod_networks".uuid = $networkUUID AND
- $userID IN (
- SELECT id FROM "papod_users"
- WHERE id = $userID AND deleted = false
- ) AND
+ uuid = ? AND
+ deleted = false AND
+ ? IN probing_user AND
(
- "papod_networks".type IN ('public', 'unlisted') OR
- $userID IN (
+ type IN ('public', 'unlisted') OR
+ ? IN (
SELECT user_id FROM "papod_members"
WHERE
- user_id = $userID AND
- network_id = "papod_networks".id
+ user_id = ? AND
+ network_id IN target_network AND
+ status != 'removed'
)
);
-
+ %!(EXTRA string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod, string=papod)
-- networks.sql:
-- write:
-- read:
- -- FIXME papod
+ WITH current_user AS (
+ SELECT id, deleted FROM "papod_users" WHERE id = ?
+ )
+ SELECT
+ "papod_networks".id,
+ "papod_networks".timestamp,
+ "papod_networks".uuid,
+ "papod_networks".name,
+ "papod_networks".description,
+ "papod_networks".type,
+ (SELECT deleted FROM current_user)
+ FROM "papod_networks"
+ JOIN "papod_members" ON
+ "papod_networks".id = "papod_members".network_id
+ WHERE (
+ "papod_networks".type = 'public' OR
+ "papod_networks".id IN (
+ SELECT network_id FROM "papod_members"
+ WHERE user_id IN (SELECT id FROM current_user)
+ )
+ ) AND "papod_networks".deleted = false
+ ORDER BY "papod_networks".id;
-- setNetwork.sql:
-- write:
- -- FIXME papod
+ UPDATE "papod_networks"
+ SET
+ name = ?,
+ description = ?,
+ type = ?
+ WHERE id = ? AND deleted = false
+ RETURNING (
+ SELECT CASE WHEN EXISTS (
+ SELECT role from "papod_member_roles"
+ WHERE
+ member_id = ? AND
+ role IN (
+ 'admin',
+ 'network-settings-update'
+ ) AND ? IN (
+ SELECT network_id
+ FROM "papod_members"
+ WHERE
+ id = ? AND
+ status = 'active'
+ )
+ ) THEN true ELSE RAISE(
+ ABORT,
+ 'member not allowed to update network data'
+ ) END
+ );
+
-- read:
-- nipNetwork.sql:
-- write:
- -- FIXME papod
+ WITH target_network AS (
+ SELECT network_id AS id
+ FROM "papod_members"
+ WHERE
+ id = ? AND
+ status = 'active'
+ )
+ UPDATE "papod_networks"
+ SET deleted = true
+ WHERE id IN target_network AND deleted = false
+ RETURNING (
+ SELECT CASE WHEN EXISTS (
+ SELECT role FROM "papod_member_roles"
+ WHERE
+ member_id = ? AND
+ role IN (
+ 'admin'
+ )
+ ) THEN true ELSE RAISE(
+ ABORT,
+ 'member not allowed to delete network'
+ ) END
+ );
-- read:
+-- membership.sql:
+-- write:
+
+-- read:
+ SELECT
+ "papod_members".id,
+ "papod_members".timestamp,
+ "papod_members".uuid,
+ "papod_members".username,
+ "papod_members".display_name,
+ "papod_members".picture_uuid,
+ "papod_members".status
+ FROM "papod_members"
+ JOIN "papod_users" ON
+ "papod_users".id = "papod_members".user_id
+ JOIN "papod_networks" ON
+ "papod_networks".id = "papod_members".network_id
+ WHERE
+ "papod_members".user_id = ? AND
+ "papod_members".network_id = ? AND
+ "papod_members".status = 'active' AND
+ "papod_users".deleted = false AND
+ "papod_networks".deleted = false;
+
+
-- addMember.sql:
-- write:
- -- FIXME papod
+ WITH target_user AS (
+ SELECT id, username, display_name, picture_uuid
+ FROM "papod_users"
+ WHERE user_uuid = ? AND deleted = false
+ ), target_network AS (
+ SELECT "papod_members".network_id AS id
+ FROM "papod_members"
+ JOIN "papod_networks" ON
+ "papod_members".network_id = "papod_networks".id
+ WHERE
+ "papod_members".id = ? AND
+ "papod_members".status = 'active' AND
+ "papod_networks".deleted = false
+ )
+ INSERT INTO "papod_members" (
+ uuid, network_id, user_id, username, display_name,
+ picture_uuid, status, active_uniq
+ ) VALUES (
+ ?,
+ (SELECT id FROM target_network),
+ (SELECT id FROM target_user),
+ ?,
+ (SELECT display_name FROM target_user),
+ (SELECT picture_uuid FROM target_user),
+ 'active',
+ 'active'
+ ) RETURNING id, timestamp, display_name, picture_uuid, status, (
+ SELECT CASE WHEN EXISTS (
+ SELECT role from "papod_member_roles"
+ WHERE
+ member_id = ? AND
+ role IN (
+ 'admin',
+ 'add-member'
+ )
+ ) THEN true ELSE RAISE(
+ ABORT,
+ 'member not allowed to add another member'
+ ) END
+ );
+
+
+-- read:
+
+-- addRole.sql:
+-- write:
+ INSERT INTO "papod_member_roles" (member_id, role)
+ VALUES (?, ?);
+
+
+-- read:
+
+-- dropRole.sql:
+-- write:
+ DELETE FROM "papod_member_roles"
+ WHERE
+ member_id = ? AND
+ role = ?
+ RETURNING 1;
-- read:
@@ -373,52 +944,175 @@
-- write:
-- read:
- -- FIXME papod
+ WITH current_network AS (
+ SELECT network_id
+ FROM "papod_members"
+ WHERE id = ?
+ )
+ SELECT
+ id,
+ timestamp,
+ username,
+ display_name,
+ picture_uuid,
+ status
+ FROM "papod_members"
+ WHERE
+ uuid = ? AND
+ network_id IN current_network;
-- members.sql:
-- write:
-- read:
- -- FIXME papod
+ WITH target_network AS (
+ SELECT "papod_members".network_id
+ FROM "papod_members"
+ JOIN "papod_networks" ON
+ "papod_members".network_id = "papod_networks".id
+ WHERE
+ "papod_members".id = ? AND
+ "papod_networks".deleted = false
+ )
+ SELECT
+ id,
+ timestamp,
+ uuid,
+ username,
+ display_name,
+ picture_uuid,
+ status
+ FROM "papod_members"
+ WHERE
+ network_id IN target_network AND
+ status = 'active';
-- editMember.sql:
-- write:
- -- FIXME papod
+ UPDATE "papod_members"
+ SET
+ status = ?
+ WHERE id = ?
+ RETURNING id;
-- read:
-- dropMember.sql:
-- write:
- -- FIXME
+ UPDATE "papod_members" SET status = 'removed'
+ WHERE uuid = ? RETURNING id;
+
+ DELETE FROM "papod_member_roles"
+ WHERE
+ role != 'creator' AND
+ member_id IN (
+ SELECT id FROM "papod_members"
+ WHERE uuid = ?
+ )
-- read:
-- addChannel.sql:
-- write:
+ WITH target_network AS (
+ SELECT network_id AS id
+ FROM "papod_members"
+ WHERE id = ?
+ )
INSERT INTO "papod_channels" (
- uuid, public_name, label, description, virtual
- ) VALUES (?, ?, ?, ?, ?) RETURNING id, timestamp;
+ uuid,
+ network_id,
+ public_name,
+ label,
+ description,
+ virtual
+ ) VALUES (
+ ?,
+ (SELECT id FROM target_network),
+ ?,
+ ?,
+ ?,
+ ?
+ ) RETURNING id, timestamp;
+
+ WITH new_channel AS (
+ SELECT id FROM "papod_channels" WHERE uuid = ?
+ )
+ INSERT INTO "papod_participants" (channel_id, member_id)
+ VALUES (
+ (SELECT id FROM new_channel),
+ ?
+ );
-- read:
+ SELECT id, timestamp FROM "papod_channels"
+ WHERE uuid = ?;
+
-- channels.sql:
-- write:
-- read:
- -- FIXME papod
+ WITH current_network AS (
+ SELECT network_id AS id
+ FROM "papod_members"
+ WHERE id = ?
+ ), member_private_channels AS (
+ SELECT channel_id AS id
+ FROM "papod_participants"
+ WHERE member_id = ?
+ )
+ SELECT
+ id,
+ timestamp,
+ uuid,
+ public_name,
+ label,
+ description,
+ virtual
+ FROM "papod_channels"
+ WHERE
+ network_id IN current_network AND
+ (
+ public_name IS NOT NULL OR
+ id IN member_private_channels
+ )
+ ORDER BY id;
--- topic.sql:
+-- setChannel.sql:
-- write:
- -- FIXME papod
+ WITH participant_channel AS (
+ SELECT channel_id AS id
+ FROM "papod_participants"
+ WHERE
+ member_id = ? AND
+ channel_id = ?
+ )
+ UPDATE "papod_channels"
+ SET
+ description = ?,
+ public_name = ?
+ WHERE id IN participant_channel
+ RETURNING id;
-- read:
+ SELECT (
+ SELECT network_id AS id
+ FROM "papod_channels"
+ WHERE id = ?
+ ) AS channel_network_id, (
+ SELECT network_id AS id
+ FROM "papod_members"
+ WHERE id = ?
+ ) AS member_network_id;
+
-- endChannel.sql:
-- write:
@@ -429,14 +1123,47 @@
-- join.sql:
-- write:
- -- FIXME papod
+ WITH target_channel AS (
+ SELECT id
+ FROM "papod_channels"
+ WHERE
+ uuid = ? AND
+ public_name IS NOT NULL
+ )
+ INSERT INTO "papod_participants" (channel_id, member_id)
+ VALUES (
+ (SELECT id FROM target_channel),
+ ?
+ ) RETURNING id;
-- read:
+ SELECT (
+ SELECT network_id AS id
+ FROM "papod_channels"
+ WHERE
+ uuid = ? AND
+ public_name IS NOT NULL
+ ) AS channel_network_id, (
+ SELECT network_id AS id
+ FROM "papod_members" WHERE id = ?
+ ) AS member_network_id;
+
-- part.sql:
-- write:
- -- FIXME papod
+ WITH target_channel AS (
+ SELECT id
+ FROM "papod_channels"
+ WHERE
+ id = ? AND
+ virtual = false
+ )
+ DELETE FROM "papod_participants"
+ WHERE
+ member_id = ? AND
+ channel_id IN target_channel
+ RETURNING 1;
-- read:
@@ -451,12 +1178,16 @@
-- addEvent.sql:
-- write:
INSERT INTO "papod_channel_events" (
- uuid, channel_id, connection_uuid, type, payload
+ uuid, channel_id, source_uuid, source_type,
+ source_metadata, type, payload, metadata
) VALUES (
?,
(SELECT id FROM "papod_channels" WHERE uuid = ?),
?,
?,
+ ?,
+ ?,
+ ?,
?
) RETURNING id, timestamp;
@@ -477,7 +1208,7 @@
"papod_channel_events".timestamp,
"papod_channel_events".uuid,
"papod_channels".uuid,
- "papod_channel_events".connection_uuid,
+ -- "papod_channel_events".connection_uuid,
"papod_channel_events".type,
"papod_channel_events".payload
FROM "papod_channel_events"