Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Finish up refactoring. Yay!

  • Loading branch information...
commit e6413ca878debb6d924e98f0ec109118ae23d4e4 1 parent fbf1edf
@theory authored
View
12 fixtures/users.copy
@@ -1,7 +1,7 @@
-COPY users FROM STDIN CSV;
-theory,yroeht,2010-07-19 18:30:05.988213+00
-jrivers,srevirj,2010-07-19 18:30:05.988213+00
-drickles,selkcird,2010-07-19 18:31:02.84838+00
-gmarx,xramg,2010-07-19 18:32:23.85843+00
-mali,ilam,2010-07-19 18:33:54.24654+00
+COPY users_ FROM STDIN CSV;
+theory,yroeht,2010-07-19 18:30:05.988213+00,
+jrivers,srevirj,2010-07-19 18:30:05.988213+00,
+drickles,selkcird,2010-07-19 18:31:02.84838+00,
+gmarx,xramg,2010-07-19 18:32:23.85843+00,
+mali,ilam,2010-07-19 18:33:54.24654+00,
\.
View
3  sql/013-privs.sql
@@ -1 +1,2 @@
-CREATE USER fliprapp;
+CREATE USER fliprapp
+ WITH LOGIN;
View
16 sql/033-refactor.sql
@@ -0,0 +1,16 @@
+BEGIN;
+
+ ALTER TABLE users
+RENAME timestamp TO created_at;
+
+ALTER TABLE users
+ ADD birth_date DATE;
+
+ ALTER TABLE users
+RENAME TO users_;
+
+CREATE VIEW users AS
+SELECT *, COALESCE(birth_date::timestamptz, created_at) AS timestamp
+ FROM users_;
+
+COMMIT;
View
37 sql/034-refactor.sql
@@ -0,0 +1,37 @@
+BEGIN;
+
+ ALTER TABLE users
+RENAME timestamp TO created_at;
+
+ALTER TABLE users
+ ADD birth_date DATE;
+
+ ALTER TABLE users
+RENAME TO users_;
+
+CREATE VIEW users AS
+SELECT *, COALESCE(birth_date::timestamptz, created_at) AS timestamp
+ FROM users_;
+
+CREATE OR REPLACE FUNCTION ins_user(
+ nickname TEXT,
+ password TEXT
+) RETURNS VOID LANGUAGE SQL SECURITY DEFINER AS $$
+ INSERT INTO users_ values($1, crypt($2, gen_salt('md5')));
+$$;
+
+CREATE OR REPLACE FUNCTION upd_pass(
+ nick TEXT,
+ oldpass TEXT,
+ newpass TEXT
+) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$
+BEGIN
+ UPDATE users_
+ SET password = crypt($3, gen_salt('md5'))
+ WHERE nickname = $1
+ AND password = crypt($2, password);
+ RETURN FOUND;
+END;
+$$;
+
+COMMIT;
View
40 sql/035-refactor.sql
@@ -0,0 +1,40 @@
+BEGIN;
+
+ ALTER TABLE users
+RENAME timestamp TO created_at;
+
+ALTER TABLE users
+ ADD birth_date DATE;
+
+ ALTER TABLE users
+RENAME TO users_;
+
+CREATE VIEW users AS
+SELECT *, COALESCE(birth_date::timestamptz, created_at) AS timestamp
+ FROM users_;
+
+CREATE OR REPLACE FUNCTION ins_user(
+ nickname TEXT,
+ password TEXT
+) RETURNS VOID LANGUAGE SQL SECURITY DEFINER AS $$
+ INSERT INTO users_ values($1, crypt($2, gen_salt('md5')));
+$$;
+
+CREATE OR REPLACE FUNCTION upd_pass(
+ nick TEXT,
+ oldpass TEXT,
+ newpass TEXT
+) RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$
+BEGIN
+ UPDATE users_
+ SET password = crypt($3, gen_salt('md5'))
+ WHERE nickname = $1
+ AND password = crypt($2, password);
+ RETURN FOUND;
+END;
+$$;
+
+REVOKE SELECT ON users_ FROM fliprapp;
+GRANT SELECT ON users TO fliprapp;
+
+COMMIT;
View
BIN  tddd.key
Binary file not shown
View
160 tests/043-schema.pg
@@ -0,0 +1,160 @@
+SET search_path = public,tap;
+
+BEGIN;
+--SELECT plan( 5 );
+SELECT * FROM no_plan();
+
+SELECT tables_are(
+ 'public',
+ ARRAY[ 'users', 'flips', 'ignored' ]
+);
+
+-- Test users.
+SELECT has_table( 'users_' );
+SELECT has_pk( 'users_' );
+
+SELECT has_column( 'users_', 'nickname' );
+SELECT col_type_is( 'users_', 'nickname', 'text' );
+SELECT col_hasnt_default( 'users_', 'nickname' );
+SELECT col_is_pk( 'users_', 'nickname' );
+
+SELECT has_column( 'users_', 'password' );
+SELECT col_type_is( 'users_', 'password', 'text' );
+SELECT col_not_null( 'users_', 'password' );
+SELECT col_hasnt_default( 'users_', 'password' );
+
+SELECT has_column( 'users_', 'created_at' );
+SELECT col_type_is( 'users_', 'created_at', 'timestamp with time zone' );
+SELECT col_not_null( 'users_', 'created_at' );
+SELECT col_has_default( 'users_', 'created_at' );
+SELECT col_default_is( 'users_', 'created_at', 'now()' );
+
+SELECT hasnt_column( 'users_', 'timestamp' );
+
+SELECT has_column( 'users_', 'birth_date' );
+SELECT col_type_is( 'users_', 'birth_date', 'date' );
+SELECT col_is_null( 'users_', 'birth_date' );
+SELECT col_hasnt_default( 'users_', 'birth_date' );
+
+SELECT has_view('users');
+
+SELECT has_column( 'users', 'nickname' );
+SELECT col_type_is( 'users', 'nickname', 'text' );
+SELECT has_column( 'users', 'password' );
+SELECT col_type_is( 'users', 'password', 'text' );
+SELECT has_column( 'users', 'timestamp' );
+SELECT col_type_is( 'users', 'timestamp', 'timestamp with time zone' );
+SELECT has_column( 'users', 'created_at' );
+SELECT col_type_is( 'users', 'created_at', 'timestamp with time zone' );
+SELECT has_column( 'users', 'birth_date' );
+SELECT col_type_is( 'users', 'birth_date', 'date' );
+
+SELECT has_function('crypt');
+SELECT has_function('gen_salt');
+
+-- Test BODY domain.
+SELECT has_domain('body');
+SELECT domain_type_is('body', 'text');
+CREATE TEMPORARY TABLE foo (body body);
+SELECT lives_ok(
+ $$INSERT INTO foo VALUES('whatever'); $$,
+ 'Should be able to insert a short body value'
+);
+
+SELECT lives_ok(
+ $$ INSERT INTO foo VALUES(lpad('', 180, 'xy')); $$,
+ 'Should be able to insert 180 character body'
+);
+
+SELECT throws_ok(
+ $$ INSERT INTO foo VALUES(lpad('', 181, 'xy')); $$,
+ '23514',
+ NULL,
+ 'Body domain should reject 181 chars'
+);
+
+-- Test flips.
+SELECT has_table( 'flips' );
+SELECT has_pk( 'flips' );
+SELECT has_fk( 'flips' );
+
+SELECT has_column( 'flips', 'id' );
+SELECT col_type_is( 'flips', 'id', 'text' );
+SELECT col_hasnt_default( 'flips', 'id' );
+SELECT col_is_pk( 'flips', 'id' );
+
+SELECT has_column( 'flips', 'nickname' );
+SELECT col_type_is( 'flips', 'nickname', 'text' );
+SELECT col_not_null( 'flips', 'nickname' );
+SELECT col_hasnt_default( 'flips', 'nickname' );
+SELECT col_is_fk( 'flips', 'nickname' );
+SELECT fk_ok( 'flips', 'nickname', 'users', 'nickname');
+
+SELECT has_column( 'flips', 'body' );
+SELECT col_type_is( 'flips', 'body', 'body' );
+SELECT col_not_null( 'flips', 'body' );
+SELECT col_has_default( 'flips', 'body' );
+SELECT col_default_is( 'flips', 'body', '');
+
+SELECT has_column( 'flips', 'timestamp' );
+SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' );
+SELECT col_not_null( 'flips', 'timestamp' );
+SELECT col_has_default( 'flips', 'timestamp' );
+SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()');
+
+SELECT has_column( 'flips', 'tsv' );
+SELECT col_type_is( 'flips', 'tsv', 'tsvector' );
+SELECT col_not_null( 'flips', 'tsv' );
+SELECT col_hasnt_default( 'flips', 'tsv' );
+
+SELECT has_index('flips', 'flip_body_fti', 'tsv'::name);
+SELECT index_is_type('flips', 'flip_body_fti', 'gin');
+
+SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name);
+SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree');
+
+SELECT ins_user('theory', '****');
+INSERT INTO flips (id, nickname, body)
+VALUES ('a', 'theory', 'this is a test');
+
+SELECT has_trigger('flips', 'flip_fti');
+SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger');
+
+SELECT is(
+ tsv, to_tsvector('english', 'this is a test'),
+ 'The trigger should work'
+) FROM flips WHERE nickname = 'theory';
+
+-- Test ignored.
+SELECT has_table( 'ignored' );
+SELECT has_pk( 'ignored' );
+SELECT has_fk( 'ignored' );
+
+SELECT has_column( 'ignored', 'user_nick' );
+SELECT col_type_is( 'ignored', 'user_nick', 'text' );
+SELECT col_not_null( 'ignored', 'user_nick' );
+SELECT col_hasnt_default( 'ignored', 'user_nick' );
+SELECT col_is_fk( 'ignored', 'user_nick' );
+SELECT fk_ok( 'ignored', 'user_nick', 'users', 'nickname');
+
+SELECT has_column( 'ignored', 'ignored_nick' );
+SELECT col_type_is( 'ignored', 'ignored_nick', 'text' );
+SELECT col_not_null( 'ignored', 'ignored_nick' );
+SELECT col_hasnt_default( 'ignored', 'ignored_nick' );
+SELECT col_is_fk( 'ignored', 'ignored_nick' );
+SELECT fk_ok( 'ignored', 'ignored_nick', 'users', 'nickname');
+
+SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']);
+
+SELECT has_index(
+ 'ignored', 'ignored_user_nick_idx', 'user_nick'::name
+);
+SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree');
+
+SELECT has_index(
+ 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name
+);
+SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree');
+
+SELECT finish();
+ROLLBACK;
View
160 tests/044-schema.pg
@@ -0,0 +1,160 @@
+SET search_path = public,tap;
+
+BEGIN;
+--SELECT plan( 5 );
+SELECT * FROM no_plan();
+
+SELECT tables_are(
+ 'public',
+ ARRAY[ 'users_', 'flips', 'ignored' ]
+);
+
+-- Test users.
+SELECT has_table( 'users_' );
+SELECT has_pk( 'users_' );
+
+SELECT has_column( 'users_', 'nickname' );
+SELECT col_type_is( 'users_', 'nickname', 'text' );
+SELECT col_hasnt_default( 'users_', 'nickname' );
+SELECT col_is_pk( 'users_', 'nickname' );
+
+SELECT has_column( 'users_', 'password' );
+SELECT col_type_is( 'users_', 'password', 'text' );
+SELECT col_not_null( 'users_', 'password' );
+SELECT col_hasnt_default( 'users_', 'password' );
+
+SELECT has_column( 'users_', 'created_at' );
+SELECT col_type_is( 'users_', 'created_at', 'timestamp with time zone' );
+SELECT col_not_null( 'users_', 'created_at' );
+SELECT col_has_default( 'users_', 'created_at' );
+SELECT col_default_is( 'users_', 'created_at', 'now()' );
+
+SELECT hasnt_column( 'users_', 'timestamp' );
+
+SELECT has_column( 'users_', 'birth_date' );
+SELECT col_type_is( 'users_', 'birth_date', 'date' );
+SELECT col_is_null( 'users_', 'birth_date' );
+SELECT col_hasnt_default( 'users_', 'birth_date' );
+
+SELECT has_view('users');
+
+SELECT has_column( 'users', 'nickname' );
+SELECT col_type_is( 'users', 'nickname', 'text' );
+SELECT has_column( 'users', 'password' );
+SELECT col_type_is( 'users', 'password', 'text' );
+SELECT has_column( 'users', 'timestamp' );
+SELECT col_type_is( 'users', 'timestamp', 'timestamp with time zone' );
+SELECT has_column( 'users', 'created_at' );
+SELECT col_type_is( 'users', 'created_at', 'timestamp with time zone' );
+SELECT has_column( 'users', 'birth_date' );
+SELECT col_type_is( 'users', 'birth_date', 'date' );
+
+SELECT has_function('crypt');
+SELECT has_function('gen_salt');
+
+-- Test BODY domain.
+SELECT has_domain('body');
+SELECT domain_type_is('body', 'text');
+CREATE TEMPORARY TABLE foo (body body);
+SELECT lives_ok(
+ $$INSERT INTO foo VALUES('whatever'); $$,
+ 'Should be able to insert a short body value'
+);
+
+SELECT lives_ok(
+ $$ INSERT INTO foo VALUES(lpad('', 180, 'xy')); $$,
+ 'Should be able to insert 180 character body'
+);
+
+SELECT throws_ok(
+ $$ INSERT INTO foo VALUES(lpad('', 181, 'xy')); $$,
+ '23514',
+ NULL,
+ 'Body domain should reject 181 chars'
+);
+
+-- Test flips.
+SELECT has_table( 'flips' );
+SELECT has_pk( 'flips' );
+SELECT has_fk( 'flips' );
+
+SELECT has_column( 'flips', 'id' );
+SELECT col_type_is( 'flips', 'id', 'text' );
+SELECT col_hasnt_default( 'flips', 'id' );
+SELECT col_is_pk( 'flips', 'id' );
+
+SELECT has_column( 'flips', 'nickname' );
+SELECT col_type_is( 'flips', 'nickname', 'text' );
+SELECT col_not_null( 'flips', 'nickname' );
+SELECT col_hasnt_default( 'flips', 'nickname' );
+SELECT col_is_fk( 'flips', 'nickname' );
+SELECT fk_ok( 'flips', 'nickname', 'users_', 'nickname');
+
+SELECT has_column( 'flips', 'body' );
+SELECT col_type_is( 'flips', 'body', 'body' );
+SELECT col_not_null( 'flips', 'body' );
+SELECT col_has_default( 'flips', 'body' );
+SELECT col_default_is( 'flips', 'body', '');
+
+SELECT has_column( 'flips', 'timestamp' );
+SELECT col_type_is( 'flips', 'timestamp', 'timestamp with time zone' );
+SELECT col_not_null( 'flips', 'timestamp' );
+SELECT col_has_default( 'flips', 'timestamp' );
+SELECT col_default_is( 'flips', 'timestamp', 'clock_timestamp()');
+
+SELECT has_column( 'flips', 'tsv' );
+SELECT col_type_is( 'flips', 'tsv', 'tsvector' );
+SELECT col_not_null( 'flips', 'tsv' );
+SELECT col_hasnt_default( 'flips', 'tsv' );
+
+SELECT has_index('flips', 'flip_body_fti', 'tsv'::name);
+SELECT index_is_type('flips', 'flip_body_fti', 'gin');
+
+SELECT has_index('flips', 'flip_timestamp_idx', 'timestamp'::name);
+SELECT index_is_type('flips', 'flip_timestamp_idx', 'btree');
+
+SELECT ins_user('theory', '****');
+INSERT INTO flips (id, nickname, body)
+VALUES ('a', 'theory', 'this is a test');
+
+SELECT has_trigger('flips', 'flip_fti');
+SELECT trigger_is( 'flips', 'flip_fti', 'tsvector_update_trigger');
+
+SELECT is(
+ tsv, to_tsvector('english', 'this is a test'),
+ 'The trigger should work'
+) FROM flips WHERE nickname = 'theory';
+
+-- Test ignored.
+SELECT has_table( 'ignored' );
+SELECT has_pk( 'ignored' );
+SELECT has_fk( 'ignored' );
+
+SELECT has_column( 'ignored', 'user_nick' );
+SELECT col_type_is( 'ignored', 'user_nick', 'text' );
+SELECT col_not_null( 'ignored', 'user_nick' );
+SELECT col_hasnt_default( 'ignored', 'user_nick' );
+SELECT col_is_fk( 'ignored', 'user_nick' );
+SELECT fk_ok( 'ignored', 'user_nick', 'users_', 'nickname');
+
+SELECT has_column( 'ignored', 'ignored_nick' );
+SELECT col_type_is( 'ignored', 'ignored_nick', 'text' );
+SELECT col_not_null( 'ignored', 'ignored_nick' );
+SELECT col_hasnt_default( 'ignored', 'ignored_nick' );
+SELECT col_is_fk( 'ignored', 'ignored_nick' );
+SELECT fk_ok( 'ignored', 'ignored_nick', 'users_', 'nickname');
+
+SELECT col_is_pk('ignored', ARRAY['user_nick', 'ignored_nick']);
+
+SELECT has_index(
+ 'ignored', 'ignored_user_nick_idx', 'user_nick'::name
+);
+SELECT index_is_type('ignored', 'ignored_user_nick_idx', 'btree');
+
+SELECT has_index(
+ 'ignored', 'ignored_ignored_nick_idx', 'ignored_nick'::name
+);
+SELECT index_is_type('ignored', 'ignored_ignored_nick_idx', 'btree');
+
+SELECT finish();
+ROLLBACK;
View
133 tests/045-userfunc.pg
@@ -0,0 +1,133 @@
+SET search_path = public,tap;
+
+BEGIN;
+--SELECT plan( 5 );
+SELECT * FROM no_plan();
+
+SELECT has_function('ins_user');
+SELECT has_function('ins_user', ARRAY['text', 'text']);
+SELECT function_returns('ins_user', 'void');
+SELECT function_lang_is('ins_user', 'sql');
+
+SELECT is(COUNT(*)::int, 0, 'Should have no users')
+ FROM users;
+SELECT lives_ok(
+ $$ SELECT ins_user('theory', 'wet blanket') $$,
+ 'Execution of ins_user() should not die'
+);
+SELECT is(COUNT(*)::int, 1, 'Should now have one user')
+ FROM users;
+
+SELECT isnt(
+ password, 'wet blanket',
+ 'Password should not be clear text'
+) FROM users WHERE nickname = 'theory';
+
+SELECT isnt(
+ password, 'theory',
+ 'Password should not be nickname'
+) FROM users WHERE nickname = 'theory';
+
+SELECT lives_ok(
+ $$ SELECT ins_user('strongrrl', 'wet blanket') $$,
+ 'Insert user "strongrrl"'
+);
+SELECT is(COUNT(*)::int, 2, 'Should now have two users')
+ FROM users;
+
+SELECT isnt(
+ (SELECT password FROM users WHERE nickname = 'strongrrl'),
+ (SELECT password FROM users WHERE nickname = 'theory'),
+ 'Same password should not match'
+);
+
+SELECT unalike(
+ password,
+ '%' || nickname || '%',
+ 'Password should not contain nickname'
+) FROM users;
+
+SELECT unalike(
+ password,
+ '%wet blanket%',
+ 'Password should not contain clear text'
+) FROM users;
+
+SELECT is(
+ password,
+ crypt('wet blanket', password),
+ 'Password should match crypted password'
+) FROM users;
+
+SELECT has_language('plpgsql');
+SELECT has_function('upd_pass');
+SELECT has_function('upd_pass', ARRAY['text', 'text', 'text']);
+SELECT function_returns('upd_pass', 'boolean');
+SELECT function_lang_is('upd_pass', 'plpgsql');
+
+SELECT ok(
+ NOT upd_pass('nobody', 'foo', 'bar'),
+ 'upd_pass() should return false for nonexistent user'
+);
+
+SELECT ok(
+ NOT upd_pass('theory', 'foo', 'bar'),
+ 'upd_pass() should return false for invalid old pass'
+);
+
+SELECT is(
+ password,
+ crypt('wet blanket', password),
+ 'Password should be unchanged'
+) FROM users WHERE nickname = 'theory';
+
+SELECT ok(
+ upd_pass('theory', 'wet blanket', 'pgtap rulez'),
+ 'upd_pass() should return true for proper args'
+);
+
+SELECT is(
+ password,
+ crypt('pgtap rulez', password),
+ 'Password should now be changed'
+) FROM users WHERE nickname = 'theory';
+
+-- Try with limited permission role.
+GRANT USAGE ON SCHEMA tap TO fliprapp;
+SET ROLE fliprapp;
+
+SELECT lives_ok(
+ $$ SELECT ins_user('anna', 'blue sea') $$,
+ 'Insert user as fliprapp'
+);
+
+SELECT is(
+ password,
+ crypt('blue sea', password),
+ 'User created by fliprapp should exist'
+) FROM users WHERE nickname = 'anna';
+
+SELECT ok(
+ upd_pass('anna', 'blue sea', 'red sky'),
+ 'Update password as fliprapp'
+);
+
+SELECT is(
+ password,
+ crypt('red sky', password),
+ 'Password updated by fliprapp should be correct'
+) FROM users WHERE nickname = 'anna';
+
+
+SELECT throws_ok(
+ $$ INSERT INTO users_ VALUES ('foo', 'bar') $$,
+ 42501 -- permission denied
+);
+
+SELECT throws_ok(
+ $$ UPDATE users_ SET password = 'foo' $$,
+ 42501 -- permission denied
+);
+
+SELECT finish();
+ROLLBACK;
View
77 tests/046-fixtures.pg
@@ -0,0 +1,77 @@
+SET search_path = public,tap;
+
+BEGIN;
+SELECT plan( 10 );
+
+-- Test user fixtures
+\i fixtures/users.copy
+
+SELECT is(
+ COUNT(*)::INT, 5,
+ 'Should have five user fixtures'
+) FROM users;
+
+SELECT is(
+ users_.*,
+ ROW('theory','yroeht','2010-07-19 18:30:05.988213+00',NULL)::users_,
+ 'User "theory" should look right'
+) FROM users_ WHERE nickname = 'theory';
+
+SELECT is(
+ users_.*,
+ ROW('mali','ilam','2010-07-19 18:33:54.24654+00',NULL)::users_,
+ 'User "mali" should look right'
+) FROM users_ WHERE nickname = 'mali';
+
+-- Test flip fixtures
+\i fixtures/flips.copy
+
+SELECT is(
+ COUNT(*)::INT, 15,
+ 'Should have 15 flip fixtures'
+) FROM flips;
+
+\set body '\'If I found you floating in my pool, I’d punish my dog.\''
+
+SELECT is(
+ flips.*,
+ ROW('a','jrivers',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips,
+ 'Joanie flip should be present'
+) FROM flips WHERE id = 'a';
+
+\set body '\'I’ve had a perfectly wonderful evening. But this wasn’t it.\''
+
+SELECT is(
+ flips.*,
+ ROW('o','gmarx',:body,'2010-07-19 11:01:03.306399+00',to_tsvector(:body))::flips,
+ 'Groucho flip should be present'
+) FROM flips WHERE id = 'o';
+
+-- Test ignored fixtures
+\i fixtures/ignored.copy
+
+SELECT is(
+ COUNT(*)::INT, 3,
+ 'Should have 3 ignored fixtures'
+) FROM ignored;
+
+SELECT ok(EXISTS(
+ SELECT true FROM ignored
+ WHERE user_nick = 'jrivers'
+ AND ignored_nick = 'drickles'
+), 'Joan should ignore Don' );
+
+SELECT ok(EXISTS(
+ SELECT true FROM ignored
+ WHERE user_nick = 'drickles'
+ AND ignored_nick = 'jrivers'
+), 'Don should ignore Joan' );
+
+SELECT ok(EXISTS(
+ SELECT true FROM ignored
+ WHERE user_nick = 'jrivers'
+ AND ignored_nick = 'gmarx'
+), 'Joan should ignore Groucho' );
+
+SELECT * FROM finish();
+ROLLBACK;
Please sign in to comment.
Something went wrong with that request. Please try again.