package papod import ( "bufio" "crypto/rand" "database/sql" "errors" "fmt" "io" "os" "reflect" "strings" "time" "golite" "guuid" g "gobang" ) 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, ×tr, &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, ×tr, &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, ×tr, &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) g.TErrorIf(err) return string(buffer) } func mkstring() string { return mknstring(32) } func test_defaultPrefix() { g.TestStart("defaultPrefix") g.Testing("the defaultPrefix is valid", func() { g.TErrorIf(g.ValidateSQLTablePrefix(defaultPrefix)) }) } func test_tryRollback() { g.TestStart("tryRollback()") myErr := errors.New("bottom error") db, err := sql.Open(golite.DriverName, golite.InMemory) g.TErrorIf(err) defer db.Close() g.Testing("the error is propagated if rollback doesn't fail", func() { tx, err := db.Begin() g.TErrorIf(err) err = tryRollback(tx, myErr) g.TAssertEqual(err, myErr) }) g.Testing("a wrapped error when rollback fails", func() { tx, err := db.Begin() g.TErrorIf(err) err = tx.Commit() g.TErrorIf(err) err = tryRollback(tx, myErr) g.TAssertEqual(reflect.DeepEqual(err, myErr), false) g.TAssertEqual(errors.Is(err, myErr), true) }) } func test_inTx() { g.TestStart("inTx()") db, err := sql.Open(golite.DriverName, golite.InMemory) g.TErrorIf(err) defer db.Close() g.Testing("when fn() errors, we propagate it", func() { myErr := errors.New("to be propagated") err := inTx(db, func(tx *sql.Tx) error { return myErr }) g.TAssertEqual(err, myErr) }) g.Testing("on nil error we get nil", func() { err := inTx(db, func(tx *sql.Tx) error { return nil }) g.TErrorIf(err) }) } func test_createTables() { g.TestStart("createTables()") db, err := sql.Open(golite.DriverName, golite.InMemory) g.TErrorIf(err) defer db.Close() g.Testing("tables exist afterwards", func() { const tmpl_read = ` SELECT id FROM "%s_channel_events" LIMIT 1; ` qRead := fmt.Sprintf(tmpl_read, defaultPrefix) _, err := db.Exec(qRead) g.TErrorNil(err) err = createTables(db, defaultPrefix) g.TErrorIf(err) _, err = db.Exec(qRead) g.TErrorIf(err) }) g.Testing("we can do it multiple times", func() { g.TErrorIf(g.SomeError( createTables(db, defaultPrefix), createTables(db, defaultPrefix), createTables(db, defaultPrefix), )) }) } func test_createUserStmt() { g.TestStart("createUserStmt()") 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) g.TErrorIf(createUserErr) defer g.SomeFnError( createUserClose, db.Close, ) userChanges := makeUserChanges(db, prefix) g.Testing("userID's must be unique", func() { newUser := newUserT{ uuid: guuid.New(), } _, err1 := createUser(newUser) _, err2 := createUser(newUser) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) g.Testing("a new user starts without a pictureID", func() { newUser := newUserT{ uuid: guuid.New(), } user, err := createUser(newUser) g.TErrorIf(err) g.TAssertEqual(user.pictureID == nil, true) }) g.Testing("the database fills default and generated values", func() { newUser := newUserT{ uuid: guuid.New(), username: "the username", displayName: "the display name", } user, err := createUser(newUser) g.TErrorIf(err) g.TAssertEqual(user.id == 0, false) g.TAssertEqual(user.timestamp == time.Time{}, false) g.TAssertEqual(user.uuid, newUser.uuid) g.TAssertEqual(user.username, "the username") g.TAssertEqual(user.displayName, "the display name") }) g.Testing("users can have duplicate names and usernames", func() { username := mkstring() displayName := mkstring() newUser1 := newUserT{ uuid: guuid.New(), username: username, displayName: displayName, } newUser2 := newUserT{ uuid: guuid.New(), username: username, displayName: displayName, } _, err1 := createUser(newUser1) _, err2 := createUser(newUser2) g.TErrorIf(err1) 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(), createUserClose(), createUserClose(), )) }) } func test_userByUUIDStmt() { g.TestStart("userByUUIDStmt()") 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) userByUUID, userByUUIDClose, userByUUIDErr := userByUUIDStmt(cfg) deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) g.TErrorIf(g.SomeError( createUserErr, deleteUserErr, userByUUIDErr, )) defer g.SomeFnError( createUserClose, deleteUserClose, userByUUIDClose, db.Close, ) g.Testing("when a user doesn't exist, we get sql.ErrNoRows", func() { _, err := userByUUID(guuid.New()) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("after creating, we can retrieve the user", func() { newUser := newUserT{ uuid: guuid.New(), username: mkstring(), displayName: mkstring(), } user1, err := createUser(newUser) g.TErrorIf(err) user2, err := userByUUID(newUser.uuid) g.TErrorIf(err) g.TAssertEqual(user2.uuid, newUser.uuid) g.TAssertEqual(user2.username, newUser.username) g.TAssertEqual(user2.displayName, newUser.displayName) g.TAssertEqual(user2.pictureID == nil, true) g.TAssertEqual(user2, user1) }) g.Testing("we can't retrieve a user once deleted",func() { newUser := newUserT{ uuid: guuid.New(), } _, err := createUser(newUser) g.TErrorIf(err) _, err = userByUUID(newUser.uuid) g.TErrorIf(err) err = deleteUser(newUser.uuid) g.TErrorIf(err) _, err = userByUUID(newUser.uuid) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("no error if closed more than once", func() { g.TErrorIf(g.SomeError( userByUUIDClose(), userByUUIDClose(), userByUUIDClose(), )) }) } func test_updateUserStmt() { g.TestStart("updateUserStmt()") 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) userByUUID, userByUUIDClose, userByUUIDErr := userByUUIDStmt(cfg) updateUser, updateUserClose, updateUserErr := updateUserStmt(cfg) deleteUser, deleteUserClose, deleteUserErr := deleteUserStmt(cfg) g.TErrorIf(g.SomeError( createUserErr, userByUUIDErr, updateUserErr, deleteUserErr, )) defer g.SomeFnError( createUserClose, userByUUIDClose, updateUserClose, deleteUserClose, db.Close, ) create := func() userT { newUser := newUserT{ uuid: guuid.New(), username: mkstring(), displayName: mkstring(), } user, err := createUser(newUser) g.TErrorIf(err) return user } userChanges := makeUserChanges(db, prefix) g.Testing("a user needs to exist to be updated", func() { virtualUser := userT{ id: 1234, } g.TAssertEqual(updateUser(virtualUser), sql.ErrNoRows) }) g.Testing("after updating, fetching gives us the newer data", func() { newUser := newUserT{ uuid: guuid.New(), username: "first username", displayName: "first display name", } user1, err := createUser(newUser) g.TErrorIf(err) g.TAssertEqual(user1.username, newUser.username) g.TAssertEqual(user1.displayName, newUser.displayName) user2 := user1 user2.username = "second username" user2.displayName = "second display name" err = updateUser(user2) g.TErrorIf(err) g.TAssertEqual(user2.id, user1.id) g.TAssertEqual(user2.timestamp, user1.timestamp) g.TAssertEqual(user2.uuid, user1.uuid) user3, err := userByUUID(user2.uuid) g.TErrorIf(err) g.TAssertEqual(user3, user2) g.TAssertEqual(user3.username, "second username") g.TAssertEqual(user3.displayName, "second display name") }) g.Testing("can't update a deleted user", func() { user := create() err = deleteUser(user.uuid) g.TErrorIf(err) err = updateUser(user) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("we can add (and remove) a picture to the user", func() { user1 := create() g.TAssertEqual(user1.pictureID == nil, true) pictureID := guuid.New() user2 := user1 user2.pictureID = &pictureID err = updateUser(user2) g.TErrorIf(err) g.TAssertEqual(user2.pictureID == nil, false) user3, err := userByUUID(user1.uuid) g.TErrorIf(err) g.TAssertEqual(user3.pictureID == nil, false) g.TAssertEqual(user3, user2) user4 := user3 user4.pictureID = nil err = updateUser(user4) g.TErrorIf(err) user5, err := userByUUID(user1.uuid) g.TErrorIf(err) g.TAssertEqual(user5.pictureID == nil, true) g.TAssertEqual(user5, user4) }) g.Testing("we can't update the timestamp or uuid", func() { user1 := create() user2 := user1 user2.timestamp = user2.timestamp.Add(time.Minute * 1) user2.uuid = guuid.New() err := updateUser(user2) g.TErrorIf(err) user3, err := userByUUID(user1.uuid) g.TErrorIf(err) 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(), updateUserClose(), updateUserClose(), )) }) } func test_deleteUserStmt() { g.TestStart("deleteUserStmt()") 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) g.TErrorIf(g.SomeError( createUserErr, deleteUserErr, )) 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()) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("error if deleted more than once", func() { newUser := newUserT{ uuid: guuid.New(), } _, err := createUser(newUser) g.TErrorIf(err) err1 := deleteUser(newUser.uuid) err2 := deleteUser(newUser.uuid) g.TErrorIf(err1) 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(), deleteUserClose(), deleteUserClose(), )) }) } func test_addNetworkStmt() { g.TestStart("addNetworkStmt()") 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) 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 { newUser := newUserT{ uuid: guuid.New(), } user, err := createUser(newUser) g.TErrorIf(err) 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 network", func() { creator := create() newNetwork := newNetworkT{ uuid: guuid.New(), name: "the network name", description: "the network description", type_: NetworkType_Unlisted, } 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.name, "the network name") g.TAssertEqual(network.description, "the network description") g.TAssertEqual(network.type_, NetworkType_Unlisted) }) g.Testing("the creator needs to exist", func() { newNetwork := newNetworkT{ uuid: guuid.New(), type_: NetworkType_Unlisted, } virtualUser := userT{ id: 1234, } _, err := addNetwork(virtualUser, newNetwork, guuid.New()) g.TAssertEqual( err.(golite.Error).ExtendedCode, golite.ErrConstraintNotNull, ) }) g.Testing("we can't add the same network twice", func() { creator := create() newNetwork := newNetworkT{ uuid: guuid.New(), name: mkstring(), type_: NetworkType_Unlisted, } _, err1 := addNetwork(creator, newNetwork, guuid.New()) _, err2 := addNetwork(creator, newNetwork, guuid.New()) g.TErrorIf(err1) g.TAssertEqual( err2.(golite.Error).ExtendedCode, golite.ErrConstraintUnique, ) }) g.Testing("a user can create multiple networks", func() { creator := create() newNetwork1 := newNetworkT{ uuid: guuid.New(), type_: NetworkType_Unlisted, } newNetwork2 := newNetworkT{ uuid: guuid.New(), type_: NetworkType_Unlisted, } _, err1 := addNetwork(creator, newNetwork1, guuid.New()) _, err2 := addNetwork(creator, newNetwork2, guuid.New()) g.TErrorIf(err1) g.TErrorIf(err2) }) g.Testing("a deleted user can't create a network", func() { creator := create() newNetwork1 := newNetworkT{ uuid: guuid.New(), type_: NetworkType_Unlisted, } newNetwork2 := newNetworkT{ uuid: guuid.New(), type_: NetworkType_Unlisted, } _, err := addNetwork(creator, newNetwork1, guuid.New()) g.TErrorIf(err) err = deleteUser(creator.uuid) g.TErrorIf(err) _, 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(), addNetworkClose(), addNetworkClose(), )) }) } func test_getNetworkStmt() { g.TestStart("getNetworkStmt()") 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) getNetwork, getNetworkClose, getNetworkErr := getNetworkStmt(cfg) membership, membershipClose, membershipErr := membershipStmt(cfg) addMember, addMemberClose, addMemberErr := addMemberStmt(cfg) dropMember, dropMemberClose, dropMemberErr := dropMemberStmt(cfg) g.TErrorIf(g.SomeError( createUserErr, deleteUserErr, addNetworkErr, getNetworkErr, membershipErr, addMemberErr, dropMemberErr, )) defer g.SomeFnError( createUserClose, deleteUserClose, addNetworkClose, getNetworkClose, 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, type_ NetworkType) (networkT, memberT) { newNetwork := newNetworkT{ uuid: guuid.New(), type_: type_, } network, err := addNetwork(user, newNetwork, guuid.New()) g.TErrorIf(err) 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) network2, err := getNetwork(creator, network1.uuid) g.TErrorIf(err) g.TAssertEqual(network2, network1) }) g.Testing("a network needs to exist", func() { _, err := getNetwork(create(), guuid.New()) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("the probing member needs to exist", func() { creator := create() network, _ := add(creator, NetworkType_Public) virtualUser := userT{ id: 1234, } _, err := getNetwork(creator, network.uuid) g.TErrorIf(err) _, err = getNetwork(virtualUser, network.uuid) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("the probing user can see any public network", func() { creator := create() user := create() network, _ := add(creator, NetworkType_Public) network1, err1 := getNetwork(creator, network.uuid) network2, err2 := getNetwork(user, network.uuid) g.TErrorIf(err1) g.TErrorIf(err2) g.TAssertEqual(network1, network) g.TAssertEqual(network2, network) }) g.Testing("the probing user sees the given unlisted network", func() { creator := create() user := create() network, _ := add(creator, NetworkType_Unlisted) network1, err1 := getNetwork(creator, network.uuid) network2, err2 := getNetwork(user, network.uuid) g.TErrorIf(err1) g.TErrorIf(err2) g.TAssertEqual(network1, network) g.TAssertEqual(network2, network) }) g.Testing("the probing user can't see a private network", func() { creator := create() user := create() network, _ := add(creator, NetworkType_Private) _, err1 := getNetwork(creator, network.uuid) _, err2 := getNetwork(user, network.uuid) g.TErrorIf(err1) g.TAssertEqual(err2, sql.ErrNoRows) }) g.Testing("the probing user must be a member to see it", func() { creator := create() user := create() network, member := add(creator, NetworkType_Private) newMember := newMemberT{ userID: user.uuid, memberID: guuid.New(), username: mkstring(), } _, err := addMember(member, newMember) g.TErrorIf(err) network1, err1 := getNetwork(creator, network.uuid) network2, err2 := getNetwork(user, network.uuid) g.TErrorIf(err1) g.TErrorIf(err2) g.TAssertEqual(network1, network) g.TAssertEqual(network2, network) }) g.Testing("we can get the network if the creator was deleted", func() { creator := create() user := create() network, member := add(creator, NetworkType_Public) newMember := newMemberT{ userID: user.uuid, memberID: guuid.New(), username: mkstring(), } _, err := addMember(member, newMember) g.TErrorIf(err) network1, err := getNetwork(creator, network.uuid) g.TErrorIf(err) err = deleteUser(creator.uuid) g.TErrorIf(err) 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) _, err := getNetwork(creator, network.uuid) g.TErrorIf(err) err = deleteUser(creator.uuid) g.TErrorIf(err) _, err = getNetwork(creator, network.uuid) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("a deleted user can't get a public network", func() { user := create() network, _ := add(create(), NetworkType_Public) _, err := getNetwork(user, network.uuid) g.TErrorIf(err) err = deleteUser(user.uuid) g.TErrorIf(err) _, err = getNetwork(user, network.uuid) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("a deleted member can't get a private network", func() { creator := create() user := create() network, member := add(creator, NetworkType_Private) newMember := newMemberT{ userID: user.uuid, memberID: guuid.New(), username: mkstring(), } _, err := addMember(member, newMember) g.TErrorIf(err) _, err = getNetwork(user, network.uuid) g.TErrorIf(err) 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() user := create() network, member := add(creator, NetworkType_Private) newMember := newMemberT{ userID: user.uuid, memberID: guuid.New(), username: mkstring(), } _, err := getNetwork(user, network.uuid) g.TAssertEqual(err, sql.ErrNoRows) _, err = addMember(member, newMember) g.TErrorIf(err) _, err = getNetwork(user, network.uuid) g.TErrorIf(err) err = dropMember(member, newMember.memberID) g.TErrorIf(err) _, err = getNetwork(user, network.uuid) g.TAssertEqual(err, sql.ErrNoRows) }) g.Testing("no error if closed more than once", func() { g.TErrorIf(g.SomeError( getNetworkClose(), getNetworkClose(), getNetworkClose(), )) }) } func test_networkEach() { 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() { g.TestStart("networksStmt()") 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) networks, networksClose, networksErr := networksStmt(cfg) g.TErrorIf(g.SomeError( createUserErr, deleteUserErr, addNetworkErr, networksErr, )) defer g.SomeFnError( createUserClose, deleteUserClose, 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, 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 }) if err != nil { return nil, err } return networkList, nil } 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() { 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() { 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() { 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("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() { g.TErrorIf(g.SomeError( networksClose(), networksClose(), networksClose(), )) }) } func test_setNetworkStmt() { g.TestStart("setNetworkStmt()") 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) 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, ) 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, } memberID := guuid.New() network, err := addNetwork(user, newNetwork, memberID) g.TErrorIf(err) member, err := membership(user, network) g.TErrorIf(err) return network, member } networkChanges := makeNetworkChanges(db, prefix) g.Testing("creator can change the network", func() { creator := create() network1, member := add(creator) name := network1.name + "name suffix" network1.name = name err := setNetwork(member, network1) g.TErrorIf(err) network2, err := getNetwork(creator, network1.uuid) g.TErrorIf(err) g.TAssertEqual(network2.name, name) }) 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(`"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() { 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() 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(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, member := add(creator) network2 := network1 network2.name = "first network name" network2.description = "first network description" network2.type_ = NetworkType_Private err := setNetwork(member, network2) g.TErrorIf(err) network3, err := getNetwork(creator, network1.uuid) g.TErrorIf(err) g.TAssertEqual(network3, network2) }) g.Testing("the uuid, timestamp or creator never changes", func() { creator := create() network1, member := add(creator) network2 := network1 network2.uuid = guuid.New() network2.timestamp = time.Time{} 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() { g.TErrorIf(g.SomeError( setNetworkClose(), setNetworkClose(), setNetworkClose(), )) }) } func test_nipNetworkStmt() { 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( 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() { g.TestStart("addMemberStmt()") 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) 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(), addMemberClose(), addMemberClose(), )) }) } 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( 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() { 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() { 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( 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( editMemberClose(), editMemberClose(), editMemberClose(), )) }) } func test_dropMemberStmt() { // FIXME g.Testing("no error if closed more than once", func() { g.TErrorIf(g.SomeError( // FIXME )) }) } func test_addChannelStmt() { g.TestStart("addChannelStmt()") 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) g.TErrorIf(g.SomeError( createUserErr, addNetworkErr, membershipErr, addChannelErr, )) defer g.SomeFnError( createUserClose, addNetworkClose, membershipClose, addChannelClose, 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 } 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( addChannelClose(), addChannelClose(), addChannelClose(), )) }) } func test_channelEach() { 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() { 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( channelsClose(), channelsClose(), channelsClose(), )) }) } 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( setChannelClose(), setChannelClose(), setChannelClose(), )) }) } func test_endChannelStmt() { // FIXME g.Testing("no error if closed more than once", func() { g.TErrorIf(g.SomeError( // FIXME )) }) } 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( 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( partClose(), partClose(), partClose(), )) }) } func test_nameEach() { // FIXME } func test_namesStmt() { // FIXME g.Testing("no error if closed more than once", func() { g.TErrorIf(g.SomeError( // FIXME )) }) } func test_addEventStmt() { g.TestStart("addEventStmt()") 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) g.TErrorIf(g.SomeError( createUserErr, addNetworkErr, membershipErr, addChannelErr, addEventErr, )) defer g.SomeFnError( createUserClose, addNetworkClose, membershipClose, addChannelClose, addEventClose, 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 } g.Testing("we can create new events", 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", } event, err := addEvent(newEvent) g.TErrorIf(err) g.TAssertEqual(event.id == 0, false) g.TAssertEqual(event.timestamp == time.Time{}, false) 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("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("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() { g.TErrorIf(g.SomeError( addEventClose(), addEventClose(), addEventClose(), )) }) } func test_eventEach() { 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("allAfterStmt()") 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{ 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) g.TErrorIf(err) return event } // 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 { events = append(events, event) return nil }) g.TErrorIf(err) return events } g.Testing("after joining the channel, there are no events", func() { return // FIXME eventID := guuid.New() member := add() channel := addC(member) expected := []eventT{ // FIXME: missing "user-join" event addE(channel.uuid), addE(channel.uuid), addE(channel.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() { g.TErrorIf(g.SomeError( allAfterClose(), allAfterClose(), allAfterClose(), )) }) } func test_logMessageStmt() { g.TestStart("logMessageStmt()") // FIXME g.Testing("no error if closed more than once", func() { g.TErrorIf(g.SomeError( // FIXME )) }) } func test_initDB() { 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() { 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() { g.TestStart("splitOnCRLF()") g.Testing("we need an ending \\r\\n to get output", func() { type tableT struct{ input string expected []string } table := []tableT{ { "", nil, }, { "\r\n", []string{ "" }, }, { "abc\r\n", []string{ "abc" }, }, { "abc\r\n ", []string{ "abc" }, }, { "abc\r\n This gets ignored.\n", []string{ "abc" }, }, { "abc\r\n \r\n", []string{ "abc", " " }, }, { " \r\n \r\n", []string{ " ", " " }, }, { "aaa\r\nbbb\r\nccc\r\n", []string{ "aaa", "bbb", "ccc" }, }, { "\r\nsplit \r \n CRLF\r\n\r\n", []string{ "", "split \r \n CRLF", "" }, }, } for _, entry := range table { var given []string scanner := bufio.NewScanner( strings.NewReader(entry.input), ) scanner.Split(splitOnCRLF) for scanner.Scan() { given = append(given, scanner.Text()) } err := scanner.Err() g.TErrorIf(err) g.TAssertEqual(given, entry.expected) } }) } func test_splitOnRawMessage() { g.TestStart("splitOnRawMessage()") g.Testing("empty messages get dropped", func() { type tableT struct{ input string expected []string } table := []tableT{ /* FIXME { "\r\nfirst\r\n\r\nsecond\r\n \r\n", []string{ "first", "second", " " }, }, */ { "first message\r\nsecond message\r\n", []string{ "first message", "second message" }, }, { "message 1\r\n\r\nmessage 2\r\n\r\nignored", []string{ "message 1", "message 2" }, }, } for _, entry := range table { var given []string scanner := bufio.NewScanner(strings.NewReader( entry.input, )) scanner.Split(splitOnRawMessage) for scanner.Scan() { given = append(given, scanner.Text()) } err := scanner.Err() g.TErrorIf(err) g.TAssertEqual(given, entry.expected) } }) } func test_parseMessageParams() { g.TestStart("parseMessageParams()") g.Testing("we can parse the string params", func() { type tableT struct{ input string expected []string } table := []tableT{ { "", []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 { given := parseMessageParams(entry.input) g.TAssertEqual(given, entry.expected) } }) } func test_parseMessage() { g.TestStart("parseMessage()") g.Testing("we can parse a full message", func() { type tableTOK struct{ input string expected messageT } tableOK := []tableTOK {{ "NICK joebloe ", messageT{ prefix: "", command: "NICK", params: []string{ "joebloe" }, raw: "NICK joebloe ", }, }, { "USER joebloe 0.0.0.0 joe :Joe Bloe", messageT{ prefix: "", command: "USER", params: []string{ "joebloe", "0.0.0.0", "joe", "Joe Bloe", }, raw: "USER joebloe 0.0.0.0 joe :Joe Bloe", }, }, { ":pre USER joebloe 0.0.0.0 joe :Joe Bloe", messageT{ prefix: "pre", command: "USER", params: []string{ "joebloe", "0.0.0.0", "joe", "Joe Bloe", }, raw: ":pre USER joebloe 0.0.0.0 joe :Joe Bloe", }, }, { ":pre USER joebloe 0.0.0.0 joe : Joe Bloe ", messageT{ prefix: "pre", command: "USER", params: []string{ "joebloe", "0.0.0.0", "joe", " Joe Bloe ", }, raw: ":pre USER joebloe 0.0.0.0 " + "joe : Joe Bloe ", }, }, { ":pre USER jbloe: 0:0:0:1 joe::a: : Joe Bloe ", messageT{ prefix: "pre", command: "USER", params: []string{ "jbloe:", "0:0:0:1", "joe::a:", " Joe Bloe ", }, raw: ":pre USER jbloe: 0:0:0:1 " + "joe::a: : Joe Bloe ", }, }, { ":pre USER :Joe Bloe", messageT{ prefix: "pre", command: "USER", params: []string{ "Joe Bloe" }, raw: ":pre USER :Joe Bloe", }, }, { ":pre USER : Joe Bloe", messageT{ prefix: "pre", command: "USER", params: []string{ " Joe Bloe" }, raw: ":pre USER : Joe Bloe", }, }, { ":pre USER : Joe Bloe", messageT{ prefix: "pre", command: "USER", params: []string{ " Joe Bloe" }, raw: ":pre USER : Joe Bloe", }, }, { ":pre USER : ", messageT{ prefix: "pre", command: "USER", params: []string{ " " }, raw: ":pre USER : ", }, }, { ":pre USER :", messageT{ prefix: "pre", command: "USER", params: []string{}, raw: ":pre USER :", }, }} for _, entry := range tableOK { given, err := parseMessage(entry.input) g.TErrorIf(err) g.TAssertEqual(given, entry.expected) } }) g.Testing("bad messages are rejected", func() { type tableErrorT struct{ input string expected error } parseErr := errors.New("Can't parse message") tableError := []tableErrorT{ { ":pre", parseErr, }, { ": pre", parseErr, }, { ":pre N1CK", parseErr, }, } for _, entry := range tableError { _, given := parseMessage(entry.input) g.TAssertEqual(given, entry.expected) } }) } 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 }, { "deleteUser", deleteUserSQL }, { "addNetwork", addNetworkSQL }, { "getNetwork", getNetworkSQL }, { "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 }, { "setChannel", setChannelSQL }, { "endChannel", endChannelSQL }, { "join", joinSQL }, { "part", partSQL }, { "names", namesSQL }, { "addEvent", addEventSQL }, { "allAfter", allAfterSQL }, { "logMessage", logMessageSQL }, } for _, query := range queries { q := query.fn(defaultPrefix) fmt.Printf("\n-- %s.sql:", query.name) fmt.Printf("\n-- write:%s\n", q.write) fmt.Printf("\n-- read:%s\n", q.read) } } func MainTest() { if os.Getenv("TESTING_DUMP_SQL_QUERIES") != "" { dumpQueries() return } g.Init() test_defaultPrefix() test_tryRollback() test_inTx() test_createTables() test_createUserStmt() test_userByUUIDStmt() test_updateUserStmt() test_deleteUserStmt() test_addNetworkStmt() test_getNetworkStmt() test_networkEach() test_networksStmt() test_setNetworkStmt() test_nipNetworkStmt() test_membershipStmt() test_addMemberStmt() test_addRoleStmt() test_dropRoleStmt() test_showMemberStmt() test_memberEach() test_membersStmt() test_editMemberStmt() test_dropMemberStmt() test_addChannelStmt() test_channelEach() test_channelsStmt() test_setChannelStmt() test_endChannelStmt() test_joinStmt() test_partStmt() test_nameEach() test_namesStmt() test_addEventStmt() test_eventEach() test_allAfterStmt() test_logMessageStmt() test_initDB() test_queriesTclose() test_splitOnCRLF() test_splitOnRawMessage() test_parseMessageParams() test_parseMessage() test_addTrailingSeparator() }