SlideShare uma empresa Scribd logo
1 de 899
Test Driven
           Database Development
                                               David E. Wheeler
                                            PostgreSQL Experts, Inc.


                                                         OSCON 2010
                                                        Portland OR USA

Text: Attribution-Noncommercial-Share Alike 3.0 United States:
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
Images licensed independently and © Their respective owners.
Test Driven
           Database Development
                                                  ✘
                                               David E. Wheeler
                                            PostgreSQL Experts, Inc.


                                                         OSCON 2010
                                                        Portland OR USA

Text: Attribution-Noncommercial-Share Alike 3.0 United States:
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
Images licensed independently and © Their respective owners.
David E. Wheeler



                                                         OSCON 2010
                                                        Portland OR USA

Text: Attribution-Noncommercial-Share Alike 3.0 United States:
http://creativecommons.org/licenses/by-nc-sa/3.0/us/
Images licensed independently and © Their respective owners.
David E. Wheeler



                                                       OSCON 2010
                                                      Portland OR USA

  License: Attribution-Noncommercial-Share Alike 3.0 United
States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/
Build My VC-Funded App



                                                                        ✔
        for Me for Free
                                                 David E. Wheeler
                                                CEO, Data Manager
                                               FASN Enterprises, Inc.

                                                       OSCON 2010
                                                      Portland OR USA

  License: Attribution-Noncommercial-Share Alike 3.0 United
States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/
This is Genius
This is Genius
I had this idea
This is Genius
I had this idea

Social networking is hot
This is Genius
I had this idea

Social networking is hot

Has been for too long
This is Genius
I had this idea

Social networking is hot

Has been for too long

The backlash is overdue
This is Genius
I had this idea

Social networking is hot

Has been for too long

The backlash is overdue

Getting ahead of the curve
This is Genius
I had this idea

Social networking is hot

Has been for too long

The backlash is overdue

Getting ahead of the curve

Introducing…
http://flic.kr/p/8j5gG8 © 2010 Strongrrl. All rights reserved. Used with permission.
antisocial   network

http://flic.kr/p/8j5gG8 © 2010 Strongrrl. All rights reserved. Used with permission.
How it Works




antisocial   network
How it Works
                       Microblogging platform




antisocial   network
How it Works
                       Microblogging platform

                       Everyone follows you




antisocial   network
How it Works
                       Microblogging platform

                       Everyone follows you

                       New users follow everyone




antisocial   network
How it Works
                       Microblogging platform

                       Everyone follows you

                       New users follow everyone

                       Goal: Alienate your followers




antisocial   network
How it Works
                       Microblogging platform

                       Everyone follows you

                       New users follow everyone

                       Goal: Alienate your followers

                       Get them to unfollow you




antisocial   network
How it Works
                       Microblogging platform

                       Everyone follows you

                       New users follow everyone

                       Goal: Alienate your followers

                       Get them to unfollow you

                       Leaderboard: Those with fewest followers


antisocial   network
Your Task




antisocial   network
Your Task
                       Create the database




antisocial   network
Your Task
                       Create the database

                       Use TDDD to make it right




antisocial   network
Your Task
                       Create the database

                       Use TDDD to make it right

                       Contribute to VC-funded Corp




antisocial   network
Your Task
                       Create the database

                       Use TDDD to make it right

                       Contribute to VC-funded Corp

                       Profit!




antisocial   network
Your Task
                       Create the database

                       Use TDDD to make it right

                       Contribute to VC-funded Corp

                       Profit!

                         For VC-funded Corp




antisocial   network
http://flic.kr/p/2honiQ © 2007 James Duncan Davidson. All rights reserved. Used with permission.
antisocial   network
TDDD
antisocial   network
                       WTF?
We’ll get there



antisocial   network
But first…



antisocial   network
NDA
antisocial   network
Okay, now that that’s out
                             of the way…



antisocial   network
Please organize into pairs



antisocial   network
Yes, that’s right



antisocial   network
You’re gonna do pair
                       database programming



antisocial   network
App developers partner
                             with DBAs



antisocial   network
I’m waiting…



antisocial   network
Okay, Ready?



antisocial   network
Time to Install



antisocial   network
Install PostgreSQL




antisocial   network
Install PostgreSQL
                       http:/
                            /www.postgresql.org/download/


                                      http://roadie.show.local/oscon/


                                      http:/ 10.16.2/oscon/
                                            /10.




antisocial   network
Install PostgreSQL
                       http:/
                            /www.postgresql.org/download/

                       Distribution package
                                        http://roadie.show.local/oscon/


                                        http:/ 10.16.2/oscon/
                                              /10.




antisocial   network
Install PostgreSQL
                       http:/
                            /www.postgresql.org/download/

                       Distribution package

                       Mac/BSD Ports    http://roadie.show.local/oscon/


                                        http:/ 10.16.2/oscon/
                                              /10.




antisocial   network
Install PostgreSQL
                       http:/
                            /www.postgresql.org/download/

                       Distribution package

                       Mac/BSD Ports    http://roadie.show.local/oscon/
                       Need postgresql-dev too!
                                        http:/ 10.16.2/oscon/
                                              /10.




antisocial   network
Install PostgreSQL
                       http:/
                            /www.postgresql.org/download/

                       Distribution package

                       Mac/BSD Ports    http://roadie.show.local/oscon/
                       Need postgresql-dev too!

                       Source:          http:/ 10.16.2/oscon/
                                              /10.




antisocial   network
Install PostgreSQL
                       http:/
                            /www.postgresql.org/download/

                       Distribution package

                       Mac/BSD Ports    http://roadie.show.local/oscon/
                       Need postgresql-dev too!

                       Source:          http:/ 10.16.2/oscon/
                                              /10.
                         ./configure && make && make install



antisocial   network
Install PostgreSQL
                       http:/
                            /www.postgresql.org/download/

                       Distribution package

                       Mac/BSD Ports    http://roadie.show.local/oscon/
                       Need postgresql-dev too!

                       Source:          http:/ 10.16.2/oscon/
                                              /10.
                         ./configure && make && make install

                         cd contrib && make && make install

antisocial   network
Install PostgreSQL
                       http:/
                            /www.postgresql.org/download/

                       Distribution package

                       Mac/BSD Ports    http://roadie.show.local/oscon/
                       Need postgresql-dev too!

                       Source:          http:/ 10.16.2/oscon/
                                              /10.
                         ./configure && make && make install

                         cd contrib && make && make install

                         initdb -D /usr/local/pgsql/data
antisocial   network
Install Test::Harness
Install Test::Harness

% cpan Test::Harness
Install Test::Harness

% cpan Test::Harness
          Or…
Install Test::Harness

% cpan Test::Harness
          Or…
% wget http://bit.ly/test-harness-321
Install Test::Harness

% cpan Test::Harness
          Or…
% wget http://bit.ly/test-harness-321
% tar zxf Test-Harness-3.21.tar.gz
Install Test::Harness

% cpan Test::Harness
          Or…
% wget http://bit.ly/test-harness-321
% tar zxf Test-Harness-3.21.tar.gz
% cd Test-Harness-3.21
Install Test::Harness

% cpan Test::Harness
            Or…
%   wget http://bit.ly/test-harness-321
%   tar zxf Test-Harness-3.21.tar.gz
%   cd Test-Harness-3.21
%   perl Makefile.PL
Install Test::Harness

% cpan Test::Harness
            Or…
%   wget http://bit.ly/test-harness-321
%   tar zxf Test-Harness-3.21.tar.gz
%   cd Test-Harness-3.21
%   perl Makefile.PL
%   make
Install Test::Harness

% cpan Test::Harness
            Or…
%   wget http://bit.ly/test-harness-321
%   tar zxf Test-Harness-3.21.tar.gz
%   cd Test-Harness-3.21
%   perl Makefile.PL
%   make
%   make test
Install Test::Harness

% cpan Test::Harness
            Or…
%   wget http://bit.ly/test-harness-321
%   tar zxf Test-Harness-3.21.tar.gz
%   cd Test-Harness-3.21
%   perl Makefile.PL
%   make
%   make test
%   sudo make install
Install pgTAP
Install pgTAP

% tar jxf pgtap-0.24.tar.bz2
Install pgTAP

% tar jxf pgtap-0.24.tar.bz2
% cd pgtap-0.24
Install pgTAP

% tar jxf pgtap-0.24.tar.bz2
% cd pgtap-0.24
% make TAPSCHEMA=tap Separate
                     schema
Install pgTAP

%   tar jxf pgtap-0.24.tar.bz2
%   cd pgtap-0.24
%   make TAPSCHEMA=tap
%   sudo make install
Install pgTAP

%   tar jxf pgtap-0.24.tar.bz2
%   cd pgtap-0.24
%   make TAPSCHEMA=tap
%   sudo make install
%   make installcheck
Install pgTAP

%   tar jxf pgtap-0.24.tar.bz2
%   cd pgtap-0.24
%   make TAPSCHEMA=tap
%   sudo make install
%   make installcheck
%   createdb -U postgres flipr
Install pgTAP

%   tar jxf pgtap-0.24.tar.bz2
%   cd pgtap-0.24
%   make TAPSCHEMA=tap
%   sudo make install
%   make installcheck
%   createdb -U postgres flipr
%   createlang -U postgres -d flipr plpgsql
Install pgTAP

%   tar jxf pgtap-0.24.tar.bz2
%   cd pgtap-0.24
%   make TAPSCHEMA=tap
%   sudo make install
%   make installcheck
%   createdb -U postgres flipr
%   createlang -U postgres -d flipr plpgsql
%   psql -U postgres -d flipr -f pgtap.sql
We good?



antisocial   network
First Po^^Test!



antisocial   network
pgTAP Basics




001-schema.pg
pgTAP Basics

SET search_path = public,tap;

BEGIN;

-- Plan the tests.
SELECT plan( 1 );

SELECT has_table( 'users' );

-- Clean up.
SELECT finish();
ROLLBACK;
 001-schema.pg
pgTAP Basics

SET search_path = public,tap;

BEGIN;

-- Plan the tests.
SELECT plan( 1 );

SELECT has_table( 'users' );

-- Clean up.
SELECT finish();
ROLLBACK;
 001-schema.pg
pgTAP Basics

SET search_path = public,tap;

BEGIN;

-- Plan the tests.
SELECT plan( 1 );

SELECT has_table( 'users' );

-- Clean up.
SELECT finish();
ROLLBACK;
 001-schema.pg
pgTAP Basics

SET search_path = public,tap;

BEGIN;

-- Plan the tests.
SELECT plan( 1 );

SELECT has_table( 'users' );

-- Clean up.
SELECT finish();
ROLLBACK;
 001-schema.pg
pgTAP Basics

SET search_path = public,tap;

BEGIN;

-- Plan the tests.
SELECT plan( 1 );

SELECT has_table( 'users' );

-- Clean up.
SELECT finish();
ROLLBACK;
 001-schema.pg
pgTAP Basics

SET search_path = public,tap;

BEGIN;

-- Plan the tests.
SELECT plan( 1 );

SELECT has_table( 'users' );

-- Clean up.
SELECT finish();
ROLLBACK;
 001-schema.pg
pgTAP Basics

SET search_path = public,tap;

BEGIN;

-- Plan the tests.
SELECT plan( 1 );

SELECT has_table( 'users' );

-- Clean up.
SELECT finish();
ROLLBACK;
 001-schema.pg
Run the Test
Run the Test
% pg_prove -vd flipr tests/001-schema.pg
tests/001-schema.pg .. 1/1
not ok 1 - Table users should exist
# Failed test 1: "Table users should exist"
# Looks like you failed 1 test of 1
tests/001-schema.pg .. Failed 1/1 subtests

Test Summary Report
-------------------
tests/001-schema.pg (Tests: 1 Failed: 1)
 Failed test: 1
Files=1, Tests=1, 0 wallclock secs
Result: FAIL
Run the Test
% pg_prove -vd flipr tests/001-schema.pg
tests/001-schema.pg .. 1/1
not ok 1 - Table users should exist
# Failed test 1: "Table users should exist"
# Looks like you failed 1 test of 1
tests/001-schema.pg .. Failed 1/1 subtests

Test Summary Report
-------------------
tests/001-schema.pg (Tests: 1 Failed: 1)
 Failed test: 1
Files=1, Tests=1, 0 wallclock secs
Result: FAIL
Run the Test
% pg_prove -vd flipr tests/001-schema.pg
tests/001-schema.pg .. 1/1
not ok 1 - Table users should exist
# Failed test 1: "Table users should exist"
# Looks like you failed 1 test of 1
tests/001-schema.pg .. Failed 1/1 subtests

Test Summary Report
-------------------
tests/001-schema.pg (Tests: 1 Failed: 1)
 Failed test: 1
Files=1, Tests=1, 0 wallclock secs
Result: FAIL
Run the Test
% pg_prove -vd flipr tests/001-schema.pg
tests/001-schema.pg .. 1/1
not ok 1 - Table users should exist
# Failed test 1: "Table users should exist"
# Looks like you failed 1 test of 1
tests/001-schema.pg .. Failed 1/1 subtests

Test Summary Report
-------------------
tests/001-schema.pg (Tests: 1 Failed: 1)
 Failed test: 1
Files=1, Tests=1, 0 wallclock secs
Result: FAIL
Create Users Table




001-users.sql
Create Users Table

CREATE TABLE users (
   id INT
);




 001-users.sql
Create Users Table

CREATE TABLE users (
   id INT
);




                 Bare minimum
 001-users.sql
First Pass
First Pass
% psql -d flipr -f sql/001-users.sql
% pg_prove -vd flipr tests/001-schema.pg
tests/1-schema.pg ..
1..1
ok 1 - Table users should exist
ok
All tests successful.
Files=1, Tests=1, 1 wallclock secs
Result: PASS
First Pass
% psql -d flipr -f sql/001-users.sql
% pg_prove -vd flipr tests/001-schema.pg
tests/1-schema.pg ..
1..1
ok 1 - Table users should exist
ok
All tests successful.
Files=1, Tests=1, 1 wallclock secs
Result: PASS
First Pass
% psql -d flipr -f sql/001-users.sql
% pg_prove -vd flipr tests/001-schema.pg
tests/1-schema.pg ..
1..1
ok 1 - Table users should exist
ok
All tests successful.
Files=1, Tests=1, 1 wallclock secs
Result: PASS
First Pass
% psql -d flipr -f sql/001-users.sql
% pg_prove -vd flipr tests/001-schema.pg
tests/1-schema.pg ..
1..1
ok 1 - Table users should exist
ok
All tests successful.
Files=1, Tests=1, 1 wallclock secs
Result: PASS



                              W00t!
Now…Iterate!



antisocial   network
What Columns?




antisocial   network
What Columns?
                       Nickname




antisocial   network
What Columns?
                       Nickname

                       Password




antisocial   network
What Columns?
                       Nickname

                       Password

                       Timestamp




antisocial   network
Column Testing




002-schema.pg
Column Testing

SET search_path = public,tap;

BEGIN;
SELECT plan( 5 );

SELECT tables_are( 'public', ARRAY[ 'users' ] );

SELECT      has_table( 'users' );
SELECT      has_column( 'users', 'nickname' );
SELECT      has_column( 'users', 'password' );
SELECT      has_column( 'users', 'timestamp' );

SELECT finish();
ROLLBACK;

  002-schema.pg
Column Testing

SET search_path = public,tap;

BEGIN;
SELECT plan( 5 );

SELECT tables_are( 'public', ARRAY[ 'users' ] );

SELECT      has_table( 'users' );
SELECT      has_column( 'users', 'nickname' );
SELECT      has_column( 'users', 'password' );
SELECT      has_column( 'users', 'timestamp' );

SELECT finish();
ROLLBACK;

  002-schema.pg
Column Testing

SET search_path = public,tap;

BEGIN;
SELECT plan( 5 );

SELECT tables_are( 'public', ARRAY[ 'users' ] );

SELECT      has_table( 'users' );
SELECT      has_column( 'users', 'nickname' );
SELECT      has_column( 'users', 'password' );
SELECT      has_column( 'users', 'timestamp' );

SELECT finish();
ROLLBACK;

  002-schema.pg
Column Testing

SET search_path = public,tap;

BEGIN;
SELECT plan( 5 );

SELECT tables_are( 'public', ARRAY[ 'users' ] );

SELECT      has_table( 'users' );
SELECT      has_column( 'users', 'nickname' );
SELECT      has_column( 'users', 'password' );
SELECT      has_column( 'users', 'timestamp' );

SELECT finish();
ROLLBACK;

  002-schema.pg
Column Testing

SET search_path = public,tap;

BEGIN;
SELECT plan( 5 );

SELECT tables_are( 'public', ARRAY[ 'users' ] );

SELECT      has_table( 'users' );
SELECT      has_column( 'users', 'nickname' );
SELECT      has_column( 'users', 'password' );
SELECT      has_column( 'users', 'timestamp' );

SELECT finish();
ROLLBACK;

  002-schema.pg
Column Testing

SET search_path = public,tap;

BEGIN;
SELECT plan( 5 );

SELECT tables_are( 'public', ARRAY[ 'users' ] );

SELECT      has_table( 'users' );
SELECT      has_column( 'users', 'nickname' );
SELECT      has_column( 'users', 'password' );
SELECT      has_column( 'users', 'timestamp' );

SELECT finish();
ROLLBACK;

  002-schema.pg
Run ’Em
Run ’Em
% pg_prove -vd flipr tests/002-schema.pg
tests/002-schema.pg ..
1..5
ok 1 - Schema public should have the correct tables
ok 2 - Table users should exist
not ok 3 - Column users.nickname should exist
# Failed test 3: "Column users.nickname should exist"
not ok 4 - Column users.password should exist
# Failed test 4: "Column users.password should exist"
not ok 5 - Column users."timestamp" should exist
# Failed test 5: "Column users."timestamp" should exist"
# Looks like you failed 3 tests of 5
tests/002-schema.pg .. Failed 3/5 subtests

Test Summary Report
-------------------
tests/002-schema.pg (Tests: 5 Failed: 2)
 Failed tests: 3-5
Files=1, Tests=5, 0 wallclock secs
Result: FAIL
Run ’Em
% pg_prove -vd flipr tests/002-schema.pg
tests/002-schema.pg ..
1..5
ok 1 - Schema public should have the correct tables
ok 2 - Table users should exist
not ok 3 - Column users.nickname should exist
# Failed test 3: "Column users.nickname should exist"
not ok 4 - Column users.password should exist
# Failed test 4: "Column users.password should exist"
not ok 5 - Column users."timestamp" should exist
# Failed test 5: "Column users."timestamp" should exist"
# Looks like you failed 3 tests of 5



                                       As
tests/002-schema.pg .. Failed 3/5 subtests

Test Summary Report
-------------------
tests/002-schema.pg (Tests: 5 Failed: 2)


                                       Expected
 Failed tests: 3-5
Files=1, Tests=5, 0 wallclock secs
Result: FAIL
Add the Columns




002-users.sql
Add the Columns
DROP TABLE IF EXISTS users;
CREATE TABLE users (
   nickname TEXT      PRIMARY KEY,
   password TEXT      NOT NULL,
   timestamp TIMESTAMPTZ NOT NULL DEFAULT
NOW()
);




 002-users.sql
Add the Columns
DROP TABLE IF EXISTS users;
CREATE TABLE users (
   nickname TEXT      PRIMARY KEY,
   password TEXT      NOT NULL,
   timestamp TIMESTAMPTZ NOT NULL DEFAULT
NOW()
);




 002-users.sql
                           Simple.
Run Again
Run Again
% psql -d flipr -f sql/002-users.sql
% pg_prove -vd flipr tests/002-schema.pg
tests/002-schema.pg ..
1..5
ok 1 - Schema public should have the correct tables
ok 2 - Table users should exist
ok 3 - Column users.nickname should exist
ok 4 - Column users.password should exist
ok 5 - Column users."timestamp" should exist
ok
All tests successful.
Files=1, Tests=5, 0 wallclock secs
Result: PASS
Run Again
% psql -d flipr -f sql/002-users.sql
% pg_prove -vd flipr tests/002-schema.pg
tests/002-schema.pg ..
1..5
ok 1 - Schema public should have the correct tables
ok 2 - Table users should exist
ok 3 - Column users.nickname should exist
ok 4 - Column users.password should exist
ok 5 - Column users."timestamp" should exist
ok
All tests successful.
Files=1, Tests=5, 0 wallclock secs
Result: PASS

                                 Rockin.
003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' );
SELECT   col_type_is('users', 'timestamp', 'timestamp with time zone');
SELECT   col_not_null(    'users', 'timestamp' );
SELECT   col_has_default( 'users', 'timestamp' );
SELECT   col_default_is( 'users', 'timestamp', 'now()' );

SELECT finish();
ROLLBACK;
  003-schmea.pg
Files
Files
git clone git:/
              /ec2.dagolden.com/git/oscon-tddd.git
Files
git clone git:/
              /ec2.dagolden.com/git/oscon-tddd.git

http://roadie.show.local/oscon/
Files
git clone git:/
              /ec2.dagolden.com/git/oscon-tddd.git

http://roadie.show.local/oscon/
http:/ 10.16.2/oscon/
     /10.
Files
git clone git:/
              /ec2.dagolden.com/git/oscon-tddd.git

http://roadie.show.local/oscon/
http:/ 10.16.2/oscon/
     /10.

http:/
     /github.com/theory/tddd/
Files
git clone git:/
              /ec2.dagolden.com/git/oscon-tddd.git

http://roadie.show.local/oscon/
http:/ 10.16.2/oscon/
     /10.

http:/
     /github.com/theory/tddd/

pg_prove -vd flipr -U postgres tests/002-schema.pg
Go!
Go!
% pg_prove -d flipr tests/003-schema.pg
tests/003-schema.pg .. ok
All tests successful.
Files=1, Tests=16, 0 wallclock secs
Result: PASS
Go!
% pg_prove -d flipr tests/003-schema.pg
tests/003-schema.pg .. ok
All tests successful.
Files=1, Tests=16, 0 wallclock secs
Result: PASS
Go!
% pg_prove -d flipr tests/003-schema.pg
tests/003-schema.pg .. ok
All tests successful.
Files=1, Tests=16, 0 wallclock secs
Result: PASS




                           Kick ass.
antisocial   network



Your Turn
antisocial   network



Your Turn
Create users table
antisocial   network



      Your Turn
       Create users table
          Project Repo:
http://github.com/theory/tddd/
So Far So Good




antisocial   network
So Far So Good
                       Appdev begins




antisocial   network
So Far So Good
                       Appdev begins

                         Web site




antisocial   network
So Far So Good
                       Appdev begins

                         Web site

                         API




antisocial   network
So Far So Good
                       Appdev begins

                         Web site

                         API

                       Ruh-roh




antisocial   network
So Far So Good
                       Appdev begins

                         Web site

                         API

                       Ruh-roh

                       Two code paths




antisocial   network
So Far So Good
                       Appdev begins

                         Web site

                         API

                       Ruh-roh

                       Two code paths

                       Just look at this data!


antisocial   network
Data Inconsistencies
Data Inconsistencies
flipr=# select nickname, password from users;
 nickname |          password
-----------
+----------------------------------
 agliodbs | 72b302bf297a228a75730123efef7c41
 anna     | May 13, 2005
 strongrrl | 008c5926ca861023c1d2a36653fd88e2
 theory | 008c5926ca861023c1d2a36653fd88e2
(4 rows)
Data Inconsistencies
flipr=# select nickname, password from users;
 nickname |          password
-----------
+----------------------------------
 agliodbs | 72b302bf297a228a75730123efef7c41
 anna     | May 13, 2005
 strongrrl | 008c5926ca861023c1d2a36653fd88e2
 theory | 008c5926ca861023c1d2a36653fd88e2
(4 rows)
Data Inconsistencies
flipr=# select nickname, password from users;
 nickname |          password
-----------
+----------------------------------
 agliodbs | 72b302bf297a228a75730123efef7c41
                                                ✔
 anna     | May 13, 2005
 strongrrl | 008c5926ca861023c1d2a36653fd88e2
 theory | 008c5926ca861023c1d2a36653fd88e2
(4 rows)
Data Inconsistencies
flipr=# select nickname, password from users;
 nickname |          password
-----------
+----------------------------------
 agliodbs | 72b302bf297a228a75730123efef7c41
                                                ✔
 anna     | May 13, 2005
 strongrrl | 008c5926ca861023c1d2a36653fd88e2
 theory | 008c5926ca861023c1d2a36653fd88e2
(4 rows)
Data Inconsistencies
flipr=# select nickname, password from users;
 nickname |          password
-----------
+----------------------------------
               ✘✘✘✘✘✘✘✘ Bad!
 agliodbs | 72b302bf297a228a75730123efef7c41
                                                ✔
 anna     | May 13, 2005
 strongrrl | 008c5926ca861023c1d2a36653fd88e2
 theory | 008c5926ca861023c1d2a36653fd88e2
(4 rows)
Data Inconsistencies
flipr=# select nickname, password from users;
 nickname |          password
-----------
+----------------------------------
               ✘✘✘✘✘✘✘✘ Bad!
 agliodbs | 72b302bf297a228a75730123efef7c41
                                                ✔
 anna     | May 13, 2005
 strongrrl | 008c5926ca861023c1d2a36653fd88e2
 theory | 008c5926ca861023c1d2a36653fd88e2
(4 rows)
Data Inconsistencies
flipr=# select nickname, password from users;
 nickname |          password
-----------
+----------------------------------
               ✘✘✘✘✘✘✘✘ Bad!
 agliodbs | 72b302bf297a228a75730123efef7c41
                                                  ✔
 anna
            〷〷〷〷〷〷〷〷〷
          | May 13, 2005
 strongrrl | 008c5926ca861023c1d2a36653fd88e2
 theory | 008c5926ca861023c1d2a36653fd88e2
                              We can do better!
(4 rows)
What Encryption?




antisocial   network
What Encryption?
                       Multiple interfaces




antisocial   network
What Encryption?
                       Multiple interfaces

                       Data must be consistent




antisocial   network
What Encryption?
                       Multiple interfaces

                       Data must be consistent

                       Encrypt passwords




antisocial   network
What Encryption?
                       Multiple interfaces

                       Data must be consistent

                       Encrypt passwords

                       Obscure password duplication




antisocial   network
What Encryption?
                       Multiple interfaces

                       Data must be consistent

                       Encrypt passwords

                       Obscure password duplication

                       Solution: Database functions




antisocial   network
Test For New Function




004-userfunc.pg
Test For New Function
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 finish();
ROLLBACK;




  004-userfunc.pg
Test For New Function
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 finish();
ROLLBACK;




  004-userfunc.pg
Test For New Function
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 finish();
ROLLBACK;




  004-userfunc.pg
Test For New Function
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 finish();
ROLLBACK;




  004-userfunc.pg
Run ’Em
Run ’Em
% pg_prove -d flipr tests/004-userfunc.pg
tests/004-userfunc.pg .. 1/?
not ok 1 - Function ins_user() should exist
# Failed test 1: "Function ins_user() should exist"
not ok 2 - Function ins_user(text, text) should exist
# Failed test 2: "Function ins_user(text, text) should exist"
not ok 3 - Function ins_user() should return void
# Failed test 3: "Function ins_user() should return void"
#    Function ins_user() does not exist
# Looks like you failed 3 tests of 3
tests/004-userfunc.pg .. Failed 3/3 subtests

Test Summary Report
-------------------
tests/004-userfunc.pg (Wstat: 0 Tests: 3 Failed: 3)
 Failed tests: 1-3
Files=1, Tests=3, 0 wallclock secs
Result: FAIL
Run ’Em
% pg_prove -d flipr tests/004-userfunc.pg
tests/004-userfunc.pg .. 1/?
not ok 1 - Function ins_user() should exist
# Failed test 1: "Function ins_user() should exist"
not ok 2 - Function ins_user(text, text) should exist
# Failed test 2: "Function ins_user(text, text) should exist"
not ok 3 - Function ins_user() should return void
# Failed test 3: "Function ins_user() should return void"
#    Function ins_user() does not exist
# Looks like you failed 3 tests of 3
tests/004-userfunc.pg .. Failed 3/3 subtests

Test Summary Report
-------------------
tests/004-userfunc.pg (Wstat: 0 Tests: 3 Failed: 3)
 Failed tests: 1-3
Files=1, Tests=3, 0 wallclock secs
Result: FAIL
Create It!




004-userfunc.sql
Create It!

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS '';




 004-userfunc.sql
Create It!

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS '';




                    Just enough…
 004-userfunc.sql
…To make ’em pass
…To make ’em pass

% psql -d flipr -f sql/004-userfunc.sql
CREATE FUNCTION
%
…To make ’em pass

% psql -d flipr -f sql/004-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/004-userfunc.pg
tests/004-userfunc.pg .. ok
All tests successful.
Files=1, Tests=3, 0 wallclock secs
Result: PASS
…To make ’em pass

% psql -d flipr -f sql/004-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/004-userfunc.pg
tests/004-userfunc.pg .. ok
All tests successful.
Files=1, Tests=3, 0 wallclock secs
Result: PASS




                                 Yay!
Make sure it does
                  something.
SELECT has_function('ins_user');
SELECT has_function('ins_user', ARRAY['text', 'text']);
SELECT function_returns('ins_user', 'void');




  005-userfunc.pg
Make sure it does
                  something.
SELECT has_function('ins_user');
SELECT has_function('ins_user', ARRAY['text', 'text']);
SELECT function_returns('ins_user', 'void');

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;




  005-userfunc.pg
Make sure it does
                  something.
SELECT has_function('ins_user');
SELECT has_function('ins_user', ARRAY['text', 'text']);
SELECT function_returns('ins_user', 'void');

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;




  005-userfunc.pg
Make sure it does
                  something.
SELECT has_function('ins_user');
SELECT has_function('ins_user', ARRAY['text', 'text']);
SELECT function_returns('ins_user', 'void');

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;




  005-userfunc.pg
Make sure it does
                  something.
SELECT has_function('ins_user');
SELECT has_function('ins_user', ARRAY['text', 'text']);
SELECT function_returns('ins_user', 'void');

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;




  005-userfunc.pg
Make sure it does
                  something.
SELECT has_function('ins_user');
SELECT has_function('ins_user', ARRAY['text', 'text']);
SELECT function_returns('ins_user', 'void');

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;




  005-userfunc.pg
What does that get us?
What does that get us?
% pg_prove -d flipr tests/005-userfunc.pg
tests/005-userfunc.pg .. 1/?
not ok 6 - Should now have one user
# Failed test 6: "Should now have one user"
#      have: 0
#      want: 1
# Looks like you failed 1 test of 6
tests/005-userfunc.pg .. Failed 1/6 subtests

Test Summary Report
-------------------
tests/005-userfunc.pg (Wstat: 0 Tests: 6 Failed: 1)
 Failed test: 6
Files=1, Tests=6, 1 wallclock secs
Result: FAIL
What does that get us?
% pg_prove -d flipr tests/005-userfunc.pg
tests/005-userfunc.pg .. 1/?
not ok 6 - Should now have one user
# Failed test 6: "Should now have one user"
#      have: 0
#      want: 1
# Looks like you failed 1 test of 6
tests/005-userfunc.pg .. Failed 1/6 subtests

Test Summary Report
-------------------
tests/005-userfunc.pg (Wstat: 0 Tests: 6 Failed: 1)
 Failed test: 6
Files=1, Tests=6, 1 wallclock secs
Result: FAIL
Modify for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS'';




 005-userfunc.sql
Modify for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
                   $$
) RETURNS VOID LANGUAGE SQL AS
   INSERT INTO users values($1, $2);
$$;




 005-userfunc.sql
Modify for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
                   $$
) RETURNS VOID LANGUAGE SQL AS
   INSERT INTO users values($1, $2);
$$;




                    Bare minimum
 005-userfunc.sql
…To make ’em pass
…To make ’em pass

% psql -d flipr -f sql/005-userfunc.sql
CREATE FUNCTION
%
…To make ’em pass

% psql -d flipr -f sql/005-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/005-userfunc.pg
tests/005-userfunc.pg .. ok
All tests successful.
Files=1, Tests=6, 0 wallclock secs
Result: PASS
…To make ’em pass

% psql -d flipr -f sql/005-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/005-userfunc.pg
tests/005-userfunc.pg .. ok
All tests successful.
Files=1, Tests=6, 0 wallclock secs
Result: PASS




                             Great!
Add More Assertions
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;




  006-userfunc.pg
Add More Assertions
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';




  006-userfunc.pg
Add More Assertions
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';




  006-userfunc.pg
Add More Assertions
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';




  006-userfunc.pg
Run ’Em
Run ’Em
% pg_prove -d flipr tests/006-userfunc.pg
tests/006-userfunc.pg .. 1/?
not ok 7 - Password should not be clear text
# Failed test 7: "Password should not be clear text"
#      have: wet blanket
#      want: anything else
# Looks like you failed 1 test of 7
tests/006-userfunc.pg .. Failed 1/7 subtests

Test Summary Report
-------------------
tests/006-userfunc.pg (Wstat: 0 Tests: 7 Failed: 1)
 Failed test: 7
Files=1, Tests=7, 0 wallclock secs
Result: FAIL
Run ’Em
% pg_prove -d flipr tests/006-userfunc.pg
tests/006-userfunc.pg .. 1/?
not ok 7 - Password should not be clear text
# Failed test 7: "Password should not be clear text"
#      have: wet blanket
#      want: anything else
# Looks like you failed 1 test of 7
tests/006-userfunc.pg .. Failed 1/7 subtests

Test Summary Report
-------------------
tests/006-userfunc.pg (Wstat: 0 Tests: 7 Failed: 1)
 Failed test: 7
Files=1, Tests=7, 0 wallclock secs
Result: FAIL
Modify for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, $ ); 2
$$;




 006-userfunc.sql
Modify for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, $ ); 1
$$;




 006-userfunc.sql
Modify for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, $ ); 1
$$;




                    I’m not kidding
 006-userfunc.sql
…To make ’em pass
…To make ’em pass

% psql -d flipr -f sql/006-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/006-userfunc.pg
tests/006-userfunc.pg .. ok
All tests successful.
Files=1, Tests=7, 1 wallclock secs
Result: PASS
…To make ’em pass

% psql -d flipr -f sql/006-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/006-userfunc.pg
tests/006-userfunc.pg .. ok
All tests successful.
Files=1, Tests=7, 1 wallclock secs
Result: PASS




                      So what?
Sanity Check
SELECT isnt(
   password, 'wet blanket',
   'Password should not be clear text'
) FROM users WHERE nickname = 'theory';




 007-userfunc.pg
Sanity Check
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';




 007-userfunc.pg
Sanity Check
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';




 007-userfunc.pg
Sanity Check
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';




 007-userfunc.pg
Are We Insane?
Are We Insane?
pg_prove -d flipr tests/007-userfunc.pg
tests/007-userfunc.pg .. 1/?
not ok 8 - Password should not be nickname
# Failed test 8: "Password should not be nickname"
#      have: theory
#      want: anything else
# Looks like you failed 1 test of 8
tests/007-userfunc.pg .. Failed 1/8 subtests

Test Summary Report
-------------------
tests/007-userfunc.pg (Wstat: 0 Tests: 8 Failed: 1)
 Failed test: 8
Files=1, Tests=8, 0 wallclock secs
Result: FAIL
Are We Insane?
pg_prove -d flipr tests/007-userfunc.pg
tests/007-userfunc.pg .. 1/?
not ok 8 - Password should not be nickname
# Failed test 8: "Password should not be nickname"
#      have: theory
#      want: anything else
# Looks like you failed 1 test of 8
tests/007-userfunc.pg .. Failed 1/8 subtests

Test Summary Report
-------------------
tests/007-userfunc.pg (Wstat: 0 Tests: 8 Failed: 1)
 Failed test: 8
Files=1, Tests=8, 0 wallclock secs
Result: FAIL
Change for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, $1);
$$;




 007-userfunc.sql
Change for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, 'whatever');
$$;




 007-userfunc.sql
Change for the Test

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, 'whatever');
$$;




                    That’ll do it.
 007-userfunc.sql
See ’em Pass
See ’em Pass

% psql -d flipr -f sql/007-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/007-userfunc.pg
tests/007-userfunc.pg .. ok
All tests successful.
Files=1, Tests=8, 0 wallclock secs
Result: PASS
See ’em Pass

% psql -d flipr -f sql/007-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/007-userfunc.pg
tests/007-userfunc.pg .. ok
All tests successful.
Files=1, Tests=8, 0 wallclock secs
Result: PASS




            Exciting, no?
Add Another User…
SELECT isnt(
   password, 'theory',
   'Password should not be nickname'
) FROM users WHERE nickname = 'theory';




  008-userfunc.pg
Add Another User…
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'
);

  008-userfunc.pg
Add Another User…
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'
);

  008-userfunc.pg
Add Another User…
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'
);

  008-userfunc.pg
Add Another User…
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'
);

  008-userfunc.pg
Let Us Be Inconsistent
Let Us Be Inconsistent
pg_prove -d flipr tests/008-userfunc.pg
tests/008-userfunc.pg .. 1/?
not ok 11 - Same password should not match
# Failed test 11: "Same password should not match"
#      have: whatever
#      want: anything else
# Looks like you failed 1 test of 11
tests/008-userfunc.pg .. Failed 1/11 subtests

Test Summary Report
-------------------
tests/008-userfunc.pg (Wstat: 0 Tests: 11 Failed: 1)
 Failed test: 11
Files=1, Tests=11, 0 wallclock secs
Result: FAIL
Let Us Be Inconsistent
pg_prove -d flipr tests/008-userfunc.pg
tests/008-userfunc.pg .. 1/?
not ok 11 - Same password should not match
# Failed test 11: "Same password should not match"
#      have: whatever
#      want: anything else
# Looks like you failed 1 test of 11
tests/008-userfunc.pg .. Failed 1/11 subtests

Test Summary Report
-------------------
tests/008-userfunc.pg (Wstat: 0 Tests: 11 Failed: 1)
 Failed test: 11
Files=1, Tests=11, 0 wallclock secs
Result: FAIL
Cheat Death

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, 'whatever');
$$;




 008-userfunc.sql
Cheat Death

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, $1 || $2);
$$;




 008-userfunc.sql
Cheat Death

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, $1 || $2);
$$;




                    No, seriously.
 008-userfunc.sql
Take a Pass
Take a Pass

% psql -d flipr -f sql/008-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/008-userfunc.pg
tests/008-userfunc.pg .. ok
All tests successful.
Files=1, Tests=11, 0 wallclock secs
Result: PASS
Take a Pass

% psql -d flipr -f sql/008-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/008-userfunc.pg
tests/008-userfunc.pg .. ok
All tests successful.
Files=1, Tests=11, 0 wallclock secs
Result: PASS




    Not unexpected.
Let’s Get Serious
SELECT isnt(
   (SELECT password FROM users WHERE nickname = 'strongrrl'),
   (SELECT password FROM users WHERE nickname = 'theory'),
   'Same password should not match'
);




  009-userfunc.pg
Let’s Get Serious
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;

  009-userfunc.pg
Let’s Get Serious
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;

  009-userfunc.pg
Let’s Get Serious
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;

  009-userfunc.pg
Let’s Get Serious
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;

  009-userfunc.pg
Let’s Get Serious
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;

  009-userfunc.pg
% pg_prove -d flipr tests/009-userfunc.pg
tests/009-userfunc.pg .. 1/?
not ok 12 - Password should not contain nickname
# Failed test 12: "Password should not contain nickname"
#              'theorywet blanket'
#       matches: '%theory%'
not ok 13 - Password should not contain nickname
# Failed test 13: "Password should not contain nickname"
#              'strongrrlwet blanket'
#       matches: '%strongrrl%'
not ok 14 - Password should not contain clear text
# Failed test 14: "Password should not contain clear text"
#              'theorywet blanket'
#       matches: '%wet blanket%'
not ok 15 - Password should not contain clear text
# Failed test 15: "Password should not contain clear text"
#              'strongrrlwet blanket'
#       matches: '%wet blanket%'
# Looks like you failed 4 tests of 15
tests/009-userfunc.pg .. Failed 4/15 subtests

Test Summary Report
-------------------
tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4)
 Failed tests: 12-15
Files=1, Tests=15, 0 wallclock secs
Result: FAIL
% pg_prove -d flipr tests/009-userfunc.pg
tests/009-userfunc.pg .. 1/?
not ok 12 - Password should not contain nickname
# Failed test 12: "Password should not contain nickname"
#              'theorywet blanket'
#       matches: '%theory%'
not ok 13 - Password should not contain nickname
# Failed test 13: "Password should not contain nickname"
#              'strongrrlwet blanket'
#       matches: '%strongrrl%'
not ok 14 - Password should not contain clear text
# Failed test 14: "Password should not contain clear text"
#              'theorywet blanket'
#       matches: '%wet blanket%'
not ok 15 - Password should not contain clear text
# Failed test 15: "Password should not contain clear text"
#              'strongrrlwet blanket'
#       matches: '%wet blanket%'
# Looks like you failed 4 tests of 15
tests/009-userfunc.pg .. Failed 4/15 subtests

Test Summary Report
-------------------
tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4)
 Failed tests: 12-15
Files=1, Tests=15, 0 wallclock secs
Result: FAIL
% pg_prove -d flipr tests/009-userfunc.pg
tests/009-userfunc.pg .. 1/?
not ok 12 - Password should not contain nickname
# Failed test 12: "Password should not contain nickname"
#              'theorywet blanket'
#       matches: '%theory%'
not ok 13 - Password should not contain nickname
# Failed test 13: "Password should not contain nickname"
#              'strongrrlwet blanket'
#       matches: '%strongrrl%'
not ok 14 - Password should not contain clear text
# Failed test 14: "Password should not contain clear text"
#              'theorywet blanket'
#       matches: '%wet blanket%'
not ok 15 - Password should not contain clear text
# Failed test 15: "Password should not contain clear text"
#              'strongrrlwet blanket'
#       matches: '%wet blanket%'
# Looks like you failed 4 tests of 15
tests/009-userfunc.pg .. Failed 4/15 subtests

Test Summary Report
-------------------
tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4)
 Failed tests: 12-15
Files=1, Tests=15, 0 wallclock secs
Result: FAIL
Try MD5

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, $1 || $2);
$$;




 009-userfunc.sql
Try MD5

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, md5($2));
$$;




 009-userfunc.sql
Try MD5

CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, md5($2));
$$;




                    It’s hashish.
 009-userfunc.sql
Here We Go!
Here We Go!
% psql -d flipr sql/009-userfunc.sql
% pg_prove -d flipr tests/009-userfunc.pg
tests/009-userfunc.pg .. 1/?
not ok 11 - Same password should not match
# Failed test 11: "Same password should not match"
#      have: 3c908e08d874ad9ec39bffc93b5fb983
#      want: anything else
# Looks like you failed 1 test of 15
tests/009-userfunc.pg .. Failed 1/15 subtests

Test Summary Report
-------------------
tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1)
 Failed test: 11
Files=1, Tests=15, 0 wallclock secs
Result: FAIL
Here We Go!
% psql -d flipr sql/009-userfunc.sql
% pg_prove -d flipr tests/009-userfunc.pg
tests/009-userfunc.pg .. 1/?
not ok 11 - Same password should not match
# Failed test 11: "Same password should not match"
#      have: 3c908e08d874ad9ec39bffc93b5fb983
#      want: anything else
# Looks like you failed 1 test of 15
tests/009-userfunc.pg .. Failed 1/15 subtests

Test Summary Report
-------------------    Wait, what?
tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1)
 Failed test: 11
Files=1, Tests=15, 0 wallclock secs
Result: FAIL
Here We Go!
% psql -d flipr sql/009-userfunc.sql
% pg_prove -d flipr tests/009-userfunc.pg
tests/009-userfunc.pg .. 1/?
not ok 11 - Same password should not match
# Failed test 11: "Same password should not match"
#      have: 3c908e08d874ad9ec39bffc93b5fb983
#      want: anything else
# Looks like you failed 1 test of 15
tests/009-userfunc.pg .. Failed 1/15 subtests

Test Summary Report
-------------------    Wait, what?
tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1)
 Failed test: 11
Files=1, Tests=15, 0 wallclock secs
Result: FAIL
Whaaaaa?
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;

  009-userfunc.pg
Whaaaaa?
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;

  009-userfunc.pg
Whaaaaa?
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'



                            Regression!
) FROM users;

SELECT unalike(
   password,
   '%wet blanket%',
   'Password should not contain clear text'
) FROM users;

  009-userfunc.pg
Whaaaaa?
SELECT isnt(
   (SELECT password FROM users WHERE nickname = 'strongrrl'),
   (SELECT password FROM users WHERE nickname = 'theory'),
   'Same password should not match'
);                                             Must
SELECT unalike(
   password,
                                              obscure
   '%' || nickname || '%',
   'Password should not contain nickname'
                                               dupes.

                            Regression!
) FROM users;

SELECT unalike(
   password,
   '%wet blanket%',
   'Password should not contain clear text'
) FROM users;

  009-userfunc.pg
Where Are We?




antisocial   network
Where Are We?
                       Function adds user




antisocial   network
Where Are We?
                       Function adds user

                       Nickname not in password




antisocial   network
Where Are We?
                       Function adds user

                       Nickname not in password

                       No clear text password




antisocial   network
Where Are We?
                       Function adds user

                       Nickname not in password

                       No clear text password

                       Must obscure duplicates




antisocial   network
Where Are We?
                       Function adds user

                       Nickname not in password

                       No clear text password

                       Must obscure duplicates

                       How?




antisocial   network
Where Are We?
                       Function adds user

                       Nickname not in password

                       No clear text password

                       Must obscure duplicates

                       How?

                       Solution: pg_crypto


antisocial   network
Test for pg_crypto
SELECT col_has_default( 'users', 'timestamp' );
SELECT col_default_is( 'users', 'timestamp', 'now()' );




  010-schema.pg
Test for pg_crypto
SELECT col_has_default( 'users', 'timestamp' );
SELECT col_default_is( 'users', 'timestamp', 'now()' );

SELECT has_function('crypt');
SELECT has_function('gen_salt');




  010-schema.pg
See Test Fail
See Test Fail
% pg_prove -d flipr tests/010-schema.pg
tests/010-schema.pg .. 1/?
not ok 17 - Function crypt() should exist
# Failed test 17: "Function crypt() should exist"
not ok 18 - Function gen_salt() should exist
# Failed test 18: "Function gen_salt() should exist"
# Looks like you failed 2 tests of 18
tests/010-schema.pg .. Failed 2/18 subtests

Test Summary Report
-------------------
tests/010-schema.pg (Wstat: 0 Tests: 18 Failed: 2)
 Failed tests: 17-18
Files=1, Tests=18, 0 wallclock secs
Result: FAIL
Install pg_crypto
Install pg_crypto
% export PG=/usr/local/pgsql
% psql -d flipr -f $PG/share/contrib/pgcrypto.sql
%
Install pg_crypto
% export PG=/usr/local/pgsql
% psql -d flipr -f $PG/share/contrib/pgcrypto.sql
% pg_prove -d flipr tests/004-schema.pg
tests/004-schema.pg .. ok
All tests successful.
Files=1, Tests=18, 1 wallclock secs
Result: PASS
Install pg_crypto
% export PG=/usr/local/pgsql
% psql -d flipr -f $PG/share/contrib/pgcrypto.sql
% pg_prove -d flipr tests/004-schema.pg
tests/004-schema.pg .. ok
All tests successful.
Files=1, Tests=18, 1 wallclock secs
Result: PASS


                                 Great.
Install pg_crypto
% export PG=/usr/local/pgsql
% psql -d flipr -f $PG/share/contrib/pgcrypto.sql
% pg_prove -d flipr tests/004-schema.pg
tests/004-schema.pg .. ok
All tests successful.
Files=1, Tests=18, 1 wallclock secs
Result: PASS


                  Great.
        Let’s use them!
Use pg_crypt
CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, md5($2));
$$;




  010-userfunc.sql
Use pg_crypt
CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, crypt($1, gen_salt('md5')));
$$;




  010-userfunc.sql
Use pg_crypt
CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
   INSERT INTO users values($1, crypt($1, gen_salt('md5')));
$$;




                         Cryptonite!
  010-userfunc.sql
Cryptonomicon!
Cryptonomicon!

% psql -d flipr -f sql/010-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/009-userfunc.pg
tests/009-userfunc.pg .. ok
All tests successful.
Files=1, Tests=15, 0 wallclock secs
Result: PASS
Cryptonomicon!

% psql -d flipr -f sql/010-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/009-userfunc.pg
tests/009-userfunc.pg .. ok
All tests successful.
Files=1, Tests=15, 0 wallclock secs
Result: PASS




                         Finally!
Sanity Check
SELECT unalike(
   password,
   '%wet blanket%',
   'Password should not contain clear text'
) FROM users;




 011-userfunc.pg
Sanity Check
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;


 011-userfunc.pg
Sanity Check
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;


 011-userfunc.pg
Sanity Check
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;


 011-userfunc.pg
Are We Sane?
Are We Sane?
% pg_prove -d flipr tests/011-userfunc.pg
tests/011-userfunc.pg .. 1/?
not ok 16 - Password should match crypted password
# Failed test 16: "Password should match crypted password"
#      have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK.
#      want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0
not ok 17 - Password should match crypted password
# Failed test 17: "Password should match crypted password"
#      have: $1$KCxoeJAK$APSOlalADk1akGrItOASx.
#      want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0
# Looks like you failed 2 tests of 17
tests/011-userfunc.pg .. Failed 2/17 subtests

Test Summary Report
-------------------
tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2)
 Failed tests: 16-17
Files=1, Tests=17, 0 wallclock secs
Result: FAIL
Are We Sane?
% pg_prove -d flipr tests/011-userfunc.pg
tests/011-userfunc.pg .. 1/?
not ok 16 - Password should match crypted password
# Failed test 16: "Password should match crypted password"
#      have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK.
#      want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0
not ok 17 - Password should match crypted password
# Failed test 17: "Password should match crypted password"
#      have: $1$KCxoeJAK$APSOlalADk1akGrItOASx.
#      want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0
# Looks like you failed 2 tests of 17
tests/011-userfunc.pg .. Failed 2/17 subtests

Test Summary Report
-------------------             Say what?
tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2)
 Failed tests: 16-17
Files=1, Tests=17, 0 wallclock secs
Result: FAIL
Are We Sane?
% pg_prove -d flipr tests/011-userfunc.pg
tests/011-userfunc.pg .. 1/?
not ok 16 - Password should match crypted password
# Failed test 16: "Password should match crypted password"
#      have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK.
#      want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0
not ok 17 - Password should match crypted password
# Failed test 17: "Password should match crypted password"
#      have: $1$KCxoeJAK$APSOlalADk1akGrItOASx.
#      want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0
# Looks like you failed 2 tests of 17
tests/011-userfunc.pg .. Failed 2/17 subtests

Test Summary Report
-------------------             Say what?
tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2)
 Failed tests: 16-17
Files=1, Tests=17, 0 wallclock secs
Result: FAIL
What’d We Do Wrong?
CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
                                             1
   INSERT INTO users values($1, crypt($ , gen_salt('md5')));
$$;




  011-userfunc.sql
What’d We Do Wrong?
CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
                                             2
   INSERT INTO users values($1, crypt($ , gen_salt('md5')));
$$;




  011-userfunc.sql
What’d We Do Wrong?
CREATE OR REPLACE FUNCTION ins_user(
   nickname TEXT,
   password TEXT
) RETURNS VOID LANGUAGE SQL AS $$
                                             2
   INSERT INTO users values($1, crypt($ , gen_salt('md5')));
$$;




                           Cryptonite!
  011-userfunc.sql
And…
And…

% psql -d flipr -f sql/011-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/011-userfunc.pg
tests/011-userfunc.pg .. ok
All tests successful.
Files=1, Tests=17, 1 wallclock secs
Result: PASS
And…

% psql -d flipr -f sql/011-userfunc.sql
CREATE FUNCTION
% pg_prove -d flipr tests/011-userfunc.pg
tests/011-userfunc.pg .. ok
All tests successful.
Files=1, Tests=17, 1 wallclock secs
Result: PASS




               We’re there!
Next: upd_pass()




antisocial   network
Next: upd_pass()
                       Need password updating




antisocial   network
Next: upd_pass()
                       Need password updating

                       Things to consider:




antisocial   network
Next: upd_pass()
                       Need password updating

                       Things to consider:

                         Update nonexistent user?




antisocial   network
Next: upd_pass()
                       Need password updating

                       Things to consider:

                         Update nonexistent user?

                         Update with invalid password?




antisocial   network
Next: upd_pass()
                       Need password updating

                       Things to consider:

                         Update nonexistent user?

                         Update with invalid password?

                         Was password updated?




antisocial   network
antisocial   network



Your Turn
antisocial   network



Your Turn
Create upd_pass()
antisocial   network



      Your Turn
     Create upd_pass()
          Project Repo:
http://github.com/theory/tddd/
How’d We Do?




012-userfunc.sql
How’d We Do?
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');




 012-userfunc.sql
How’d We Do?
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');




 012-userfunc.sql
How’d We Do?
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');




 012-userfunc.sql
How’d We Do?
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');




 012-userfunc.sql
How’d We Do?
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');




 012-userfunc.sql
How’d We Do?
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');




 012-userfunc.sql
How’d We Do?
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';
  012-userfunc.sql
How’d We Do?
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';
  012-userfunc.sql
How’d We Do?
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';
  012-userfunc.sql
How’d We Do?
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';
  012-userfunc.sql
How’d We Do?




012-userfunc.sql
How’d We Do?
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';




  012-userfunc.sql
How’d We Do?
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';




  012-userfunc.sql
How’d We Do?
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';




  012-userfunc.sql
What Does it Look Like?




013-privs.sql
What Does it Look Like?
CREATE OR REPLACE FUNCTION upd_pass(
   nick TEXT,
   oldpass TEXT,
   newpass TEXT
) RETURNS BOOLEAN LANGUAGE plpgsql AS $$
BEGIN
   UPDATE users
     SET password = crypt($3, gen_salt('md5'))
    WHERE nickname = $1
     AND password = crypt($2, password);
   RETURN FOUND;
END;
$$;
 013-privs.sql
What Does it Look Like?
CREATE OR REPLACE FUNCTION upd_pass(
   nick TEXT,
   oldpass TEXT,
   newpass TEXT
) RETURNS BOOLEAN LANGUAGE plpgsql AS $$
BEGIN
   UPDATE users
     SET password = crypt($3, gen_salt('md5'))
    WHERE nickname = $1
     AND password = crypt($2, password);
   RETURN FOUND;
END;
$$;
 013-privs.sql
                         Good, eh?
What About Privileges?




antisocial   network
What About Privileges?
                       Got solid functions




antisocial   network
What About Privileges?
                       Got solid functions

                       Apps still using tables




antisocial   network
What About Privileges?
                       Got solid functions

                       Apps still using tables

                       How to prevent that?




antisocial   network
What About Privileges?
                       Got solid functions

                       Apps still using tables

                       How to prevent that?

                       Privileges!




antisocial   network
What About Privileges?
                       Got solid functions

                       Apps still using tables

                       How to prevent that?

                       Privileges!

                       Deny access to table




antisocial   network
What About Privileges?
                       Got solid functions

                       Apps still using tables

                       How to prevent that?

                       Privileges!

                       Deny access to table

                       Allow access to functions


antisocial   network
Test Privileges




013-privs.pg
Test Privileges
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT has_role('fliprapp');
SELECT isnt_superuser('fliprapp');

SELECT finish();
ROLLBACK;


 013-privs.pg
Test Privileges
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT has_role('fliprapp');
SELECT isnt_superuser('fliprapp');

SELECT finish();
ROLLBACK;


 013-privs.pg
Test Privileges
SET search_path = public,tap;

BEGIN;
--SELECT plan( 5 );
SELECT * FROM no_plan();

SELECT has_role('fliprapp');
SELECT isnt_superuser('fliprapp');

SELECT finish();
ROLLBACK;


 013-privs.pg
Check Privileges
Check Privileges
% pg_prove -d flipr tests/013-privs.pg
tests/013-privs.pg .. 1/?
not ok 1 - Role fliprapp should exist
# Failed test 1: "Role fliprapp should exist"
not ok 2 - User fliprapp should not be a super user
# Failed test 2: "User fliprapp should not be a super user"
#    User fliprapp does not exist
# Looks like you failed 2 tests of 2
tests/013-privs.pg .. Failed 2/2 subtests

Test Summary Report
-------------------
tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2)
 Failed tests: 1-2
Files=1, Tests=2, 0 wallclock secs
Result: FAIL
Check Privileges
% pg_prove -d flipr tests/013-privs.pg
tests/013-privs.pg .. 1/?
not ok 1 - Role fliprapp should exist
# Failed test 1: "Role fliprapp should exist"
not ok 2 - User fliprapp should not be a super user
# Failed test 2: "User fliprapp should not be a super user"
#    User fliprapp does not exist
# Looks like you failed 2 tests of 2
tests/013-privs.pg .. Failed 2/2 subtests

Test Summary Report
-------------------
tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2)
 Failed tests: 1-2
Files=1, Tests=2, 0 wallclock secs
Result: FAIL
Check Privileges
% pg_prove -d flipr tests/013-privs.pg
tests/013-privs.pg .. 1/?
not ok 1 - Role fliprapp should exist
# Failed test 1: "Role fliprapp should exist"
not ok 2 - User fliprapp should not be a super user
# Failed test 2: "User fliprapp should not be a super user"
#    User fliprapp does not exist
# Looks like you failed 2 tests of 2
tests/013-privs.pg .. Failed 2/2 subtests

Test Summary Report
-------------------
tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2)
 Failed tests: 1-2
Files=1, Tests=2, 0 wallclock secs
Result: FAIL
Add a User




013-privs.sql
Add a User

CREATE USER fliprapp
 WITH LOGIN;




 013-privs.sql
Add a User

CREATE USER fliprapp
 WITH LOGIN;




                  Easy, right?
 013-privs.sql
User Test
User Test
% psql -d flipr -f sql/013-privs.sql
CREATE ROLE
% pg_prove -d flipr tests/013-privs.pg
tests/013-privs.pg .. ok
All tests successful.
Files=1, Tests=2, 0 wallclock secs
Result: PASS
User Test
% psql -d flipr -f sql/013-privs.sql
CREATE ROLE
% pg_prove -d flipr tests/013-privs.pg
tests/013-privs.pg .. ok
All tests successful.
Files=1, Tests=2, 0 wallclock secs
Result: PASS




                            Riiiiight.
Sanity Check
SELECT has_role('fliprapp');
SELECT isnt_superuser('fliprapp');




 014-userfunc.pg
Sanity Check
SELECT has_role('fliprapp');
SELECT isnt_superuser('fliprapp');

SELECT volatility_is('ins_user', 'volatile');
SELECT volatility_is('upd_pass', 'volatile');

SELECT is_definer('ins_user');
SELECT is_definer('upd_pass');




 014-userfunc.pg
Sanity Check
SELECT has_role('fliprapp');
SELECT isnt_superuser('fliprapp');

SELECT volatility_is('ins_user', 'volatile');
SELECT volatility_is('upd_pass', 'volatile');

SELECT is_definer('ins_user');
SELECT is_definer('upd_pass');




 014-userfunc.pg
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development
Test Driven Database Development

Mais conteúdo relacionado

Mais procurados

Python tools to deploy your machine learning models faster
Python tools to deploy your machine learning models fasterPython tools to deploy your machine learning models faster
Python tools to deploy your machine learning models fasterJeff Hale
 
GraphFrames: DataFrame-based graphs for Apache® Spark™
GraphFrames: DataFrame-based graphs for Apache® Spark™GraphFrames: DataFrame-based graphs for Apache® Spark™
GraphFrames: DataFrame-based graphs for Apache® Spark™Databricks
 
Guide to GStreamer Application Development Manual: CH1 to CH10
Guide to GStreamer Application Development Manual: CH1 to CH10Guide to GStreamer Application Development Manual: CH1 to CH10
Guide to GStreamer Application Development Manual: CH1 to CH10Wen Liao
 
In-Depth Model/View with QML
In-Depth Model/View with QMLIn-Depth Model/View with QML
In-Depth Model/View with QMLICS
 
Intro to Neo4j presentation
Intro to Neo4j presentationIntro to Neo4j presentation
Intro to Neo4j presentationjexp
 
[2D1]Elasticsearch 성능 최적화
[2D1]Elasticsearch 성능 최적화[2D1]Elasticsearch 성능 최적화
[2D1]Elasticsearch 성능 최적화NAVER D2
 
Trends_of_MLOps_tech_in_business
Trends_of_MLOps_tech_in_businessTrends_of_MLOps_tech_in_business
Trends_of_MLOps_tech_in_businessSANG WON PARK
 
Graph Databases - RedisGraph and RedisInsight
Graph Databases - RedisGraph and RedisInsightGraph Databases - RedisGraph and RedisInsight
Graph Databases - RedisGraph and RedisInsightMd. Farhan Memon
 
stackconf 2021 | Weaviate Vector Search Engine – Introduction
stackconf 2021 | Weaviate Vector Search Engine – Introductionstackconf 2021 | Weaviate Vector Search Engine – Introduction
stackconf 2021 | Weaviate Vector Search Engine – IntroductionNETWAYS
 
Machine learning with Spark
Machine learning with SparkMachine learning with Spark
Machine learning with SparkKhalid Salama
 
When Kafka Meets the Scaling and Reliability needs of World's Largest Retaile...
When Kafka Meets the Scaling and Reliability needs of World's Largest Retaile...When Kafka Meets the Scaling and Reliability needs of World's Largest Retaile...
When Kafka Meets the Scaling and Reliability needs of World's Largest Retaile...confluent
 
데브시스터즈 데이터 레이크 구축 이야기 : Data Lake architecture case study (박주홍 데이터 분석 및 인프라 팀...
데브시스터즈 데이터 레이크 구축 이야기 : Data Lake architecture case study (박주홍 데이터 분석 및 인프라 팀...데브시스터즈 데이터 레이크 구축 이야기 : Data Lake architecture case study (박주홍 데이터 분석 및 인프라 팀...
데브시스터즈 데이터 레이크 구축 이야기 : Data Lake architecture case study (박주홍 데이터 분석 및 인프라 팀...Amazon Web Services Korea
 
Django congress jp 2019 make query great again! (slide share)
Django congress jp 2019 make query great again! (slide share)Django congress jp 2019 make query great again! (slide share)
Django congress jp 2019 make query great again! (slide share)dattun
 
How Graph Databases efficiently store, manage and query connected data at s...
How Graph Databases efficiently  store, manage and query  connected data at s...How Graph Databases efficiently  store, manage and query  connected data at s...
How Graph Databases efficiently store, manage and query connected data at s...jexp
 
Introduction to DataFusion An Embeddable Query Engine Written in Rust
Introduction to DataFusion  An Embeddable Query Engine Written in RustIntroduction to DataFusion  An Embeddable Query Engine Written in Rust
Introduction to DataFusion An Embeddable Query Engine Written in RustAndrew Lamb
 
elasticsearch_적용 및 활용_정리
elasticsearch_적용 및 활용_정리elasticsearch_적용 및 활용_정리
elasticsearch_적용 및 활용_정리Junyi Song
 

Mais procurados (20)

Python tools to deploy your machine learning models faster
Python tools to deploy your machine learning models fasterPython tools to deploy your machine learning models faster
Python tools to deploy your machine learning models faster
 
GraphFrames: DataFrame-based graphs for Apache® Spark™
GraphFrames: DataFrame-based graphs for Apache® Spark™GraphFrames: DataFrame-based graphs for Apache® Spark™
GraphFrames: DataFrame-based graphs for Apache® Spark™
 
Practical Object Oriented Models In Sql
Practical Object Oriented Models In SqlPractical Object Oriented Models In Sql
Practical Object Oriented Models In Sql
 
Guide to GStreamer Application Development Manual: CH1 to CH10
Guide to GStreamer Application Development Manual: CH1 to CH10Guide to GStreamer Application Development Manual: CH1 to CH10
Guide to GStreamer Application Development Manual: CH1 to CH10
 
In-Depth Model/View with QML
In-Depth Model/View with QMLIn-Depth Model/View with QML
In-Depth Model/View with QML
 
Intro to Neo4j presentation
Intro to Neo4j presentationIntro to Neo4j presentation
Intro to Neo4j presentation
 
TiDB Introduction
TiDB IntroductionTiDB Introduction
TiDB Introduction
 
[2D1]Elasticsearch 성능 최적화
[2D1]Elasticsearch 성능 최적화[2D1]Elasticsearch 성능 최적화
[2D1]Elasticsearch 성능 최적화
 
Trends_of_MLOps_tech_in_business
Trends_of_MLOps_tech_in_businessTrends_of_MLOps_tech_in_business
Trends_of_MLOps_tech_in_business
 
Graph Databases - RedisGraph and RedisInsight
Graph Databases - RedisGraph and RedisInsightGraph Databases - RedisGraph and RedisInsight
Graph Databases - RedisGraph and RedisInsight
 
stackconf 2021 | Weaviate Vector Search Engine – Introduction
stackconf 2021 | Weaviate Vector Search Engine – Introductionstackconf 2021 | Weaviate Vector Search Engine – Introduction
stackconf 2021 | Weaviate Vector Search Engine – Introduction
 
Machine learning with Spark
Machine learning with SparkMachine learning with Spark
Machine learning with Spark
 
When Kafka Meets the Scaling and Reliability needs of World's Largest Retaile...
When Kafka Meets the Scaling and Reliability needs of World's Largest Retaile...When Kafka Meets the Scaling and Reliability needs of World's Largest Retaile...
When Kafka Meets the Scaling and Reliability needs of World's Largest Retaile...
 
데브시스터즈 데이터 레이크 구축 이야기 : Data Lake architecture case study (박주홍 데이터 분석 및 인프라 팀...
데브시스터즈 데이터 레이크 구축 이야기 : Data Lake architecture case study (박주홍 데이터 분석 및 인프라 팀...데브시스터즈 데이터 레이크 구축 이야기 : Data Lake architecture case study (박주홍 데이터 분석 및 인프라 팀...
데브시스터즈 데이터 레이크 구축 이야기 : Data Lake architecture case study (박주홍 데이터 분석 및 인프라 팀...
 
Catalyst optimizer
Catalyst optimizerCatalyst optimizer
Catalyst optimizer
 
Django congress jp 2019 make query great again! (slide share)
Django congress jp 2019 make query great again! (slide share)Django congress jp 2019 make query great again! (slide share)
Django congress jp 2019 make query great again! (slide share)
 
How Graph Databases efficiently store, manage and query connected data at s...
How Graph Databases efficiently  store, manage and query  connected data at s...How Graph Databases efficiently  store, manage and query  connected data at s...
How Graph Databases efficiently store, manage and query connected data at s...
 
Introduction to DataFusion An Embeddable Query Engine Written in Rust
Introduction to DataFusion  An Embeddable Query Engine Written in RustIntroduction to DataFusion  An Embeddable Query Engine Written in Rust
Introduction to DataFusion An Embeddable Query Engine Written in Rust
 
elasticsearch_적용 및 활용_정리
elasticsearch_적용 및 활용_정리elasticsearch_적용 및 활용_정리
elasticsearch_적용 및 활용_정리
 
Sql query patterns, optimized
Sql query patterns, optimizedSql query patterns, optimized
Sql query patterns, optimized
 

Semelhante a Test Driven Database Development

Patterns for Open Source Success
Patterns for Open Source SuccessPatterns for Open Source Success
Patterns for Open Source SuccessStephen Walli
 
A Framework for Open Source Software Success
A Framework for Open Source Software SuccessA Framework for Open Source Software Success
A Framework for Open Source Software SuccessPaula Hunter
 
Containers and Developer Defined Data Centers - Evan Powell - Keynote in Bang...
Containers and Developer Defined Data Centers - Evan Powell - Keynote in Bang...Containers and Developer Defined Data Centers - Evan Powell - Keynote in Bang...
Containers and Developer Defined Data Centers - Evan Powell - Keynote in Bang...CodeOps Technologies LLP
 
Ep keyote slides
Ep  keyote slidesEp  keyote slides
Ep keyote slidesOpenEBS
 
Decentralized Social Networks - WebVisions 2009
Decentralized Social Networks - WebVisions 2009Decentralized Social Networks - WebVisions 2009
Decentralized Social Networks - WebVisions 2009David Recordon
 
Douglas Crockford - Ajax Security
Douglas Crockford - Ajax SecurityDouglas Crockford - Ajax Security
Douglas Crockford - Ajax SecurityWeb Directions
 
Monktoberfest Fast Delivery
Monktoberfest Fast DeliveryMonktoberfest Fast Delivery
Monktoberfest Fast DeliveryAdrian Cockcroft
 
DevOps and the cloud: all hail the (developer) king - Daniel Bryant, Steve Poole
DevOps and the cloud: all hail the (developer) king - Daniel Bryant, Steve PooleDevOps and the cloud: all hail the (developer) king - Daniel Bryant, Steve Poole
DevOps and the cloud: all hail the (developer) king - Daniel Bryant, Steve PooleJAXLondon_Conference
 
"Twitter, Pray, Love" at State Policy Network 2010
"Twitter, Pray, Love" at State Policy Network 2010"Twitter, Pray, Love" at State Policy Network 2010
"Twitter, Pray, Love" at State Policy Network 2010Cord Blomquist
 
Java, Communities, and Social Networking
Java, Communities, and Social NetworkingJava, Communities, and Social Networking
Java, Communities, and Social NetworkingLou Ordorica
 
JAXLondon 2015 "DevOps and the Cloud: All Hail the (Developer) King"
JAXLondon 2015 "DevOps and the Cloud: All Hail the (Developer) King"JAXLondon 2015 "DevOps and the Cloud: All Hail the (Developer) King"
JAXLondon 2015 "DevOps and the Cloud: All Hail the (Developer) King"Daniel Bryant
 
PICS - Bootstrap Tech Talk
PICS - Bootstrap Tech TalkPICS - Bootstrap Tech Talk
PICS - Bootstrap Tech Talkchemoish
 
Identity, privacy & the Open Web
Identity, privacy & the Open WebIdentity, privacy & the Open Web
Identity, privacy & the Open Webgueste2dc25
 
Microservices for the Masses with Spring Boot and JHipster - Chicago JUG 2018
Microservices for the Masses with Spring Boot and JHipster - Chicago JUG 2018Microservices for the Masses with Spring Boot and JHipster - Chicago JUG 2018
Microservices for the Masses with Spring Boot and JHipster - Chicago JUG 2018Matt Raible
 
Open Source and the MEAN stack
Open Source and the MEAN stackOpen Source and the MEAN stack
Open Source and the MEAN stackLiran Tal
 
Fast Delivery DevOps Israel
Fast Delivery DevOps IsraelFast Delivery DevOps Israel
Fast Delivery DevOps IsraelAdrian Cockcroft
 
Teaching Elephants to Dance (Federal Audience): A Developer's Journey to Digi...
Teaching Elephants to Dance (Federal Audience): A Developer's Journey to Digi...Teaching Elephants to Dance (Federal Audience): A Developer's Journey to Digi...
Teaching Elephants to Dance (Federal Audience): A Developer's Journey to Digi...Burr Sutter
 

Semelhante a Test Driven Database Development (20)

Patterns for Open Source Success
Patterns for Open Source SuccessPatterns for Open Source Success
Patterns for Open Source Success
 
A Framework for Open Source Software Success
A Framework for Open Source Software SuccessA Framework for Open Source Software Success
A Framework for Open Source Software Success
 
Containers and Developer Defined Data Centers - Evan Powell - Keynote in Bang...
Containers and Developer Defined Data Centers - Evan Powell - Keynote in Bang...Containers and Developer Defined Data Centers - Evan Powell - Keynote in Bang...
Containers and Developer Defined Data Centers - Evan Powell - Keynote in Bang...
 
Ep keyote slides
Ep  keyote slidesEp  keyote slides
Ep keyote slides
 
Ep keyote slides
Ep  keyote slidesEp  keyote slides
Ep keyote slides
 
Decentralized Social Networks - WebVisions 2009
Decentralized Social Networks - WebVisions 2009Decentralized Social Networks - WebVisions 2009
Decentralized Social Networks - WebVisions 2009
 
Douglas Crockford - Ajax Security
Douglas Crockford - Ajax SecurityDouglas Crockford - Ajax Security
Douglas Crockford - Ajax Security
 
Monktoberfest Fast Delivery
Monktoberfest Fast DeliveryMonktoberfest Fast Delivery
Monktoberfest Fast Delivery
 
Open source and Security
Open source and SecurityOpen source and Security
Open source and Security
 
DevOps and the cloud: all hail the (developer) king - Daniel Bryant, Steve Poole
DevOps and the cloud: all hail the (developer) king - Daniel Bryant, Steve PooleDevOps and the cloud: all hail the (developer) king - Daniel Bryant, Steve Poole
DevOps and the cloud: all hail the (developer) king - Daniel Bryant, Steve Poole
 
"Twitter, Pray, Love" at State Policy Network 2010
"Twitter, Pray, Love" at State Policy Network 2010"Twitter, Pray, Love" at State Policy Network 2010
"Twitter, Pray, Love" at State Policy Network 2010
 
Java, Communities, and Social Networking
Java, Communities, and Social NetworkingJava, Communities, and Social Networking
Java, Communities, and Social Networking
 
JAXLondon 2015 "DevOps and the Cloud: All Hail the (Developer) King"
JAXLondon 2015 "DevOps and the Cloud: All Hail the (Developer) King"JAXLondon 2015 "DevOps and the Cloud: All Hail the (Developer) King"
JAXLondon 2015 "DevOps and the Cloud: All Hail the (Developer) King"
 
PICS - Bootstrap Tech Talk
PICS - Bootstrap Tech TalkPICS - Bootstrap Tech Talk
PICS - Bootstrap Tech Talk
 
Identity, privacy & the Open Web
Identity, privacy & the Open WebIdentity, privacy & the Open Web
Identity, privacy & the Open Web
 
Microservices for the Masses with Spring Boot and JHipster - Chicago JUG 2018
Microservices for the Masses with Spring Boot and JHipster - Chicago JUG 2018Microservices for the Masses with Spring Boot and JHipster - Chicago JUG 2018
Microservices for the Masses with Spring Boot and JHipster - Chicago JUG 2018
 
Open Source and the MEAN stack
Open Source and the MEAN stackOpen Source and the MEAN stack
Open Source and the MEAN stack
 
Fast Delivery DevOps Israel
Fast Delivery DevOps IsraelFast Delivery DevOps Israel
Fast Delivery DevOps Israel
 
Teaching Elephants to Dance (Federal Audience): A Developer's Journey to Digi...
Teaching Elephants to Dance (Federal Audience): A Developer's Journey to Digi...Teaching Elephants to Dance (Federal Audience): A Developer's Journey to Digi...
Teaching Elephants to Dance (Federal Audience): A Developer's Journey to Digi...
 
Agile framework Support
Agile framework SupportAgile framework Support
Agile framework Support
 

Mais de David Wheeler

Sane SQL Change Management with Sqitch
Sane SQL Change Management with SqitchSane SQL Change Management with Sqitch
Sane SQL Change Management with SqitchDavid Wheeler
 
Simple SQL Change Management with Sqitch
Simple SQL Change Management with SqitchSimple SQL Change Management with Sqitch
Simple SQL Change Management with SqitchDavid Wheeler
 
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning CBuilding and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning CDavid Wheeler
 
Releasing PostgreSQL Extension on PGXN
Releasing PostgreSQL Extension on PGXNReleasing PostgreSQL Extension on PGXN
Releasing PostgreSQL Extension on PGXNDavid Wheeler
 
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning CBuilding and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning CDavid Wheeler
 
PgTAP Best Practices
PgTAP Best PracticesPgTAP Best Practices
PgTAP Best PracticesDavid Wheeler
 

Mais de David Wheeler (6)

Sane SQL Change Management with Sqitch
Sane SQL Change Management with SqitchSane SQL Change Management with Sqitch
Sane SQL Change Management with Sqitch
 
Simple SQL Change Management with Sqitch
Simple SQL Change Management with SqitchSimple SQL Change Management with Sqitch
Simple SQL Change Management with Sqitch
 
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning CBuilding and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
 
Releasing PostgreSQL Extension on PGXN
Releasing PostgreSQL Extension on PGXNReleasing PostgreSQL Extension on PGXN
Releasing PostgreSQL Extension on PGXN
 
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning CBuilding and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
 
PgTAP Best Practices
PgTAP Best PracticesPgTAP Best Practices
PgTAP Best Practices
 

Último

A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxLoriGlavin3
 
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyesHow to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyesThousandEyes
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rick Flair
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentPim van der Noll
 
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Scott Andery
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxLoriGlavin3
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch TuesdayIvanti
 
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Mark Goldstein
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality AssuranceInflectra
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Farhan Tariq
 
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...AliaaTarek5
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 

Último (20)

A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
 
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyesHow to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
How to Effectively Monitor SD-WAN and SASE Environments with ThousandEyes
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
 
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
Enhancing User Experience - Exploring the Latest Features of Tallyman Axis Lo...
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch Tuesday
 
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...
 
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 

Test Driven Database Development

  • 1. Test Driven Database Development David E. Wheeler PostgreSQL Experts, Inc. OSCON 2010 Portland OR USA Text: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ Images licensed independently and © Their respective owners.
  • 2. Test Driven Database Development ✘ David E. Wheeler PostgreSQL Experts, Inc. OSCON 2010 Portland OR USA Text: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ Images licensed independently and © Their respective owners.
  • 3. David E. Wheeler OSCON 2010 Portland OR USA Text: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ Images licensed independently and © Their respective owners.
  • 4. David E. Wheeler OSCON 2010 Portland OR USA License: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/
  • 5. Build My VC-Funded App ✔ for Me for Free David E. Wheeler CEO, Data Manager FASN Enterprises, Inc. OSCON 2010 Portland OR USA License: Attribution-Noncommercial-Share Alike 3.0 United States: http://creativecommons.org/licenses/by-nc-sa/3.0/us/
  • 7. This is Genius I had this idea
  • 8. This is Genius I had this idea Social networking is hot
  • 9. This is Genius I had this idea Social networking is hot Has been for too long
  • 10. This is Genius I had this idea Social networking is hot Has been for too long The backlash is overdue
  • 11. This is Genius I had this idea Social networking is hot Has been for too long The backlash is overdue Getting ahead of the curve
  • 12. This is Genius I had this idea Social networking is hot Has been for too long The backlash is overdue Getting ahead of the curve Introducing…
  • 13. http://flic.kr/p/8j5gG8 © 2010 Strongrrl. All rights reserved. Used with permission.
  • 14. antisocial network http://flic.kr/p/8j5gG8 © 2010 Strongrrl. All rights reserved. Used with permission.
  • 16. How it Works Microblogging platform antisocial network
  • 17. How it Works Microblogging platform Everyone follows you antisocial network
  • 18. How it Works Microblogging platform Everyone follows you New users follow everyone antisocial network
  • 19. How it Works Microblogging platform Everyone follows you New users follow everyone Goal: Alienate your followers antisocial network
  • 20. How it Works Microblogging platform Everyone follows you New users follow everyone Goal: Alienate your followers Get them to unfollow you antisocial network
  • 21. How it Works Microblogging platform Everyone follows you New users follow everyone Goal: Alienate your followers Get them to unfollow you Leaderboard: Those with fewest followers antisocial network
  • 23. Your Task Create the database antisocial network
  • 24. Your Task Create the database Use TDDD to make it right antisocial network
  • 25. Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp antisocial network
  • 26. Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp Profit! antisocial network
  • 27. Your Task Create the database Use TDDD to make it right Contribute to VC-funded Corp Profit! For VC-funded Corp antisocial network
  • 28. http://flic.kr/p/2honiQ © 2007 James Duncan Davidson. All rights reserved. Used with permission.
  • 29. antisocial network
  • 30. TDDD antisocial network WTF?
  • 33. NDA antisocial network
  • 34. Okay, now that that’s out of the way… antisocial network
  • 35. Please organize into pairs antisocial network
  • 37. You’re gonna do pair database programming antisocial network
  • 38. App developers partner with DBAs antisocial network
  • 43. Install PostgreSQL http:/ /www.postgresql.org/download/ http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. antisocial network
  • 44. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. antisocial network
  • 45. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. antisocial network
  • 46. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! http:/ 10.16.2/oscon/ /10. antisocial network
  • 47. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! Source: http:/ 10.16.2/oscon/ /10. antisocial network
  • 48. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! Source: http:/ 10.16.2/oscon/ /10. ./configure && make && make install antisocial network
  • 49. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! Source: http:/ 10.16.2/oscon/ /10. ./configure && make && make install cd contrib && make && make install antisocial network
  • 50. Install PostgreSQL http:/ /www.postgresql.org/download/ Distribution package Mac/BSD Ports http://roadie.show.local/oscon/ Need postgresql-dev too! Source: http:/ 10.16.2/oscon/ /10. ./configure && make && make install cd contrib && make && make install initdb -D /usr/local/pgsql/data antisocial network
  • 53. Install Test::Harness % cpan Test::Harness Or…
  • 54. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321
  • 55. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz
  • 56. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21
  • 57. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21 % perl Makefile.PL
  • 58. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21 % perl Makefile.PL % make
  • 59. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21 % perl Makefile.PL % make % make test
  • 60. Install Test::Harness % cpan Test::Harness Or… % wget http://bit.ly/test-harness-321 % tar zxf Test-Harness-3.21.tar.gz % cd Test-Harness-3.21 % perl Makefile.PL % make % make test % sudo make install
  • 62. Install pgTAP % tar jxf pgtap-0.24.tar.bz2
  • 63. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24
  • 64. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap Separate schema
  • 65. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install
  • 66. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck
  • 67. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck % createdb -U postgres flipr
  • 68. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck % createdb -U postgres flipr % createlang -U postgres -d flipr plpgsql
  • 69. Install pgTAP % tar jxf pgtap-0.24.tar.bz2 % cd pgtap-0.24 % make TAPSCHEMA=tap % sudo make install % make installcheck % createdb -U postgres flipr % createlang -U postgres -d flipr plpgsql % psql -U postgres -d flipr -f pgtap.sql
  • 73. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
  • 74. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
  • 75. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
  • 76. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
  • 77. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
  • 78. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
  • 79. pgTAP Basics SET search_path = public,tap; BEGIN; -- Plan the tests. SELECT plan( 1 ); SELECT has_table( 'users' ); -- Clean up. SELECT finish(); ROLLBACK; 001-schema.pg
  • 81. Run the Test % pg_prove -vd flipr tests/001-schema.pg tests/001-schema.pg .. 1/1 not ok 1 - Table users should exist # Failed test 1: "Table users should exist" # Looks like you failed 1 test of 1 tests/001-schema.pg .. Failed 1/1 subtests Test Summary Report ------------------- tests/001-schema.pg (Tests: 1 Failed: 1) Failed test: 1 Files=1, Tests=1, 0 wallclock secs Result: FAIL
  • 82. Run the Test % pg_prove -vd flipr tests/001-schema.pg tests/001-schema.pg .. 1/1 not ok 1 - Table users should exist # Failed test 1: "Table users should exist" # Looks like you failed 1 test of 1 tests/001-schema.pg .. Failed 1/1 subtests Test Summary Report ------------------- tests/001-schema.pg (Tests: 1 Failed: 1) Failed test: 1 Files=1, Tests=1, 0 wallclock secs Result: FAIL
  • 83. Run the Test % pg_prove -vd flipr tests/001-schema.pg tests/001-schema.pg .. 1/1 not ok 1 - Table users should exist # Failed test 1: "Table users should exist" # Looks like you failed 1 test of 1 tests/001-schema.pg .. Failed 1/1 subtests Test Summary Report ------------------- tests/001-schema.pg (Tests: 1 Failed: 1) Failed test: 1 Files=1, Tests=1, 0 wallclock secs Result: FAIL
  • 84. Run the Test % pg_prove -vd flipr tests/001-schema.pg tests/001-schema.pg .. 1/1 not ok 1 - Table users should exist # Failed test 1: "Table users should exist" # Looks like you failed 1 test of 1 tests/001-schema.pg .. Failed 1/1 subtests Test Summary Report ------------------- tests/001-schema.pg (Tests: 1 Failed: 1) Failed test: 1 Files=1, Tests=1, 0 wallclock secs Result: FAIL
  • 86. Create Users Table CREATE TABLE users ( id INT ); 001-users.sql
  • 87. Create Users Table CREATE TABLE users ( id INT ); Bare minimum 001-users.sql
  • 89. First Pass % psql -d flipr -f sql/001-users.sql % pg_prove -vd flipr tests/001-schema.pg tests/1-schema.pg .. 1..1 ok 1 - Table users should exist ok All tests successful. Files=1, Tests=1, 1 wallclock secs Result: PASS
  • 90. First Pass % psql -d flipr -f sql/001-users.sql % pg_prove -vd flipr tests/001-schema.pg tests/1-schema.pg .. 1..1 ok 1 - Table users should exist ok All tests successful. Files=1, Tests=1, 1 wallclock secs Result: PASS
  • 91. First Pass % psql -d flipr -f sql/001-users.sql % pg_prove -vd flipr tests/001-schema.pg tests/1-schema.pg .. 1..1 ok 1 - Table users should exist ok All tests successful. Files=1, Tests=1, 1 wallclock secs Result: PASS
  • 92. First Pass % psql -d flipr -f sql/001-users.sql % pg_prove -vd flipr tests/001-schema.pg tests/1-schema.pg .. 1..1 ok 1 - Table users should exist ok All tests successful. Files=1, Tests=1, 1 wallclock secs Result: PASS W00t!
  • 95. What Columns? Nickname antisocial network
  • 96. What Columns? Nickname Password antisocial network
  • 97. What Columns? Nickname Password Timestamp antisocial network
  • 99. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
  • 100. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
  • 101. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
  • 102. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
  • 103. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
  • 104. Column Testing SET search_path = public,tap; BEGIN; SELECT plan( 5 ); SELECT tables_are( 'public', ARRAY[ 'users' ] ); SELECT has_table( 'users' ); SELECT has_column( 'users', 'nickname' ); SELECT has_column( 'users', 'password' ); SELECT has_column( 'users', 'timestamp' ); SELECT finish(); ROLLBACK; 002-schema.pg
  • 106. Run ’Em % pg_prove -vd flipr tests/002-schema.pg tests/002-schema.pg .. 1..5 ok 1 - Schema public should have the correct tables ok 2 - Table users should exist not ok 3 - Column users.nickname should exist # Failed test 3: "Column users.nickname should exist" not ok 4 - Column users.password should exist # Failed test 4: "Column users.password should exist" not ok 5 - Column users."timestamp" should exist # Failed test 5: "Column users."timestamp" should exist" # Looks like you failed 3 tests of 5 tests/002-schema.pg .. Failed 3/5 subtests Test Summary Report ------------------- tests/002-schema.pg (Tests: 5 Failed: 2) Failed tests: 3-5 Files=1, Tests=5, 0 wallclock secs Result: FAIL
  • 107. Run ’Em % pg_prove -vd flipr tests/002-schema.pg tests/002-schema.pg .. 1..5 ok 1 - Schema public should have the correct tables ok 2 - Table users should exist not ok 3 - Column users.nickname should exist # Failed test 3: "Column users.nickname should exist" not ok 4 - Column users.password should exist # Failed test 4: "Column users.password should exist" not ok 5 - Column users."timestamp" should exist # Failed test 5: "Column users."timestamp" should exist" # Looks like you failed 3 tests of 5 As tests/002-schema.pg .. Failed 3/5 subtests Test Summary Report ------------------- tests/002-schema.pg (Tests: 5 Failed: 2) Expected Failed tests: 3-5 Files=1, Tests=5, 0 wallclock secs Result: FAIL
  • 109. Add the Columns DROP TABLE IF EXISTS users; CREATE TABLE users ( nickname TEXT PRIMARY KEY, password TEXT NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() ); 002-users.sql
  • 110. Add the Columns DROP TABLE IF EXISTS users; CREATE TABLE users ( nickname TEXT PRIMARY KEY, password TEXT NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() ); 002-users.sql Simple.
  • 112. Run Again % psql -d flipr -f sql/002-users.sql % pg_prove -vd flipr tests/002-schema.pg tests/002-schema.pg .. 1..5 ok 1 - Schema public should have the correct tables ok 2 - Table users should exist ok 3 - Column users.nickname should exist ok 4 - Column users.password should exist ok 5 - Column users."timestamp" should exist ok All tests successful. Files=1, Tests=5, 0 wallclock secs Result: PASS
  • 113. Run Again % psql -d flipr -f sql/002-users.sql % pg_prove -vd flipr tests/002-schema.pg tests/002-schema.pg .. 1..5 ok 1 - Schema public should have the correct tables ok 2 - Table users should exist ok 3 - Column users.nickname should exist ok 4 - Column users.password should exist ok 5 - Column users."timestamp" should exist ok All tests successful. Files=1, Tests=5, 0 wallclock secs Result: PASS Rockin.
  • 115. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 116. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 117. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 118. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 119. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 120. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 121. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 122. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 123. SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT tables_are( 'public', ARRAY[ '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', 'timestamp' ); SELECT col_type_is('users', 'timestamp', 'timestamp with time zone'); SELECT col_not_null( 'users', 'timestamp' ); SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT finish(); ROLLBACK; 003-schmea.pg
  • 124. Files
  • 125. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git
  • 126. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/
  • 127. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10.
  • 128. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. http:/ /github.com/theory/tddd/
  • 129. Files git clone git:/ /ec2.dagolden.com/git/oscon-tddd.git http://roadie.show.local/oscon/ http:/ 10.16.2/oscon/ /10. http:/ /github.com/theory/tddd/ pg_prove -vd flipr -U postgres tests/002-schema.pg
  • 130. Go!
  • 131. Go! % pg_prove -d flipr tests/003-schema.pg tests/003-schema.pg .. ok All tests successful. Files=1, Tests=16, 0 wallclock secs Result: PASS
  • 132. Go! % pg_prove -d flipr tests/003-schema.pg tests/003-schema.pg .. ok All tests successful. Files=1, Tests=16, 0 wallclock secs Result: PASS
  • 133. Go! % pg_prove -d flipr tests/003-schema.pg tests/003-schema.pg .. ok All tests successful. Files=1, Tests=16, 0 wallclock secs Result: PASS Kick ass.
  • 134. antisocial network Your Turn
  • 135. antisocial network Your Turn Create users table
  • 136. antisocial network Your Turn Create users table Project Repo: http://github.com/theory/tddd/
  • 137. So Far So Good antisocial network
  • 138. So Far So Good Appdev begins antisocial network
  • 139. So Far So Good Appdev begins Web site antisocial network
  • 140. So Far So Good Appdev begins Web site API antisocial network
  • 141. So Far So Good Appdev begins Web site API Ruh-roh antisocial network
  • 142. So Far So Good Appdev begins Web site API Ruh-roh Two code paths antisocial network
  • 143. So Far So Good Appdev begins Web site API Ruh-roh Two code paths Just look at this data! antisocial network
  • 145. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • 146. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • 147. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • 148. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • 149. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- ✘✘✘✘✘✘✘✘ Bad! agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • 150. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- ✘✘✘✘✘✘✘✘ Bad! agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 (4 rows)
  • 151. Data Inconsistencies flipr=# select nickname, password from users; nickname | password ----------- +---------------------------------- ✘✘✘✘✘✘✘✘ Bad! agliodbs | 72b302bf297a228a75730123efef7c41 ✔ anna 〷〷〷〷〷〷〷〷〷 | May 13, 2005 strongrrl | 008c5926ca861023c1d2a36653fd88e2 theory | 008c5926ca861023c1d2a36653fd88e2 We can do better! (4 rows)
  • 153. What Encryption? Multiple interfaces antisocial network
  • 154. What Encryption? Multiple interfaces Data must be consistent antisocial network
  • 155. What Encryption? Multiple interfaces Data must be consistent Encrypt passwords antisocial network
  • 156. What Encryption? Multiple interfaces Data must be consistent Encrypt passwords Obscure password duplication antisocial network
  • 157. What Encryption? Multiple interfaces Data must be consistent Encrypt passwords Obscure password duplication Solution: Database functions antisocial network
  • 158. Test For New Function 004-userfunc.pg
  • 159. Test For New Function 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 finish(); ROLLBACK; 004-userfunc.pg
  • 160. Test For New Function 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 finish(); ROLLBACK; 004-userfunc.pg
  • 161. Test For New Function 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 finish(); ROLLBACK; 004-userfunc.pg
  • 162. Test For New Function 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 finish(); ROLLBACK; 004-userfunc.pg
  • 164. Run ’Em % pg_prove -d flipr tests/004-userfunc.pg tests/004-userfunc.pg .. 1/? not ok 1 - Function ins_user() should exist # Failed test 1: "Function ins_user() should exist" not ok 2 - Function ins_user(text, text) should exist # Failed test 2: "Function ins_user(text, text) should exist" not ok 3 - Function ins_user() should return void # Failed test 3: "Function ins_user() should return void" # Function ins_user() does not exist # Looks like you failed 3 tests of 3 tests/004-userfunc.pg .. Failed 3/3 subtests Test Summary Report ------------------- tests/004-userfunc.pg (Wstat: 0 Tests: 3 Failed: 3) Failed tests: 1-3 Files=1, Tests=3, 0 wallclock secs Result: FAIL
  • 165. Run ’Em % pg_prove -d flipr tests/004-userfunc.pg tests/004-userfunc.pg .. 1/? not ok 1 - Function ins_user() should exist # Failed test 1: "Function ins_user() should exist" not ok 2 - Function ins_user(text, text) should exist # Failed test 2: "Function ins_user(text, text) should exist" not ok 3 - Function ins_user() should return void # Failed test 3: "Function ins_user() should return void" # Function ins_user() does not exist # Looks like you failed 3 tests of 3 tests/004-userfunc.pg .. Failed 3/3 subtests Test Summary Report ------------------- tests/004-userfunc.pg (Wstat: 0 Tests: 3 Failed: 3) Failed tests: 1-3 Files=1, Tests=3, 0 wallclock secs Result: FAIL
  • 167. Create It! CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS ''; 004-userfunc.sql
  • 168. Create It! CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS ''; Just enough… 004-userfunc.sql
  • 170. …To make ’em pass % psql -d flipr -f sql/004-userfunc.sql CREATE FUNCTION %
  • 171. …To make ’em pass % psql -d flipr -f sql/004-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/004-userfunc.pg tests/004-userfunc.pg .. ok All tests successful. Files=1, Tests=3, 0 wallclock secs Result: PASS
  • 172. …To make ’em pass % psql -d flipr -f sql/004-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/004-userfunc.pg tests/004-userfunc.pg .. ok All tests successful. Files=1, Tests=3, 0 wallclock secs Result: PASS Yay!
  • 173. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); 005-userfunc.pg
  • 174. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); 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; 005-userfunc.pg
  • 175. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); 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; 005-userfunc.pg
  • 176. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); 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; 005-userfunc.pg
  • 177. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); 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; 005-userfunc.pg
  • 178. Make sure it does something. SELECT has_function('ins_user'); SELECT has_function('ins_user', ARRAY['text', 'text']); SELECT function_returns('ins_user', 'void'); 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; 005-userfunc.pg
  • 179. What does that get us?
  • 180. What does that get us? % pg_prove -d flipr tests/005-userfunc.pg tests/005-userfunc.pg .. 1/? not ok 6 - Should now have one user # Failed test 6: "Should now have one user" # have: 0 # want: 1 # Looks like you failed 1 test of 6 tests/005-userfunc.pg .. Failed 1/6 subtests Test Summary Report ------------------- tests/005-userfunc.pg (Wstat: 0 Tests: 6 Failed: 1) Failed test: 6 Files=1, Tests=6, 1 wallclock secs Result: FAIL
  • 181. What does that get us? % pg_prove -d flipr tests/005-userfunc.pg tests/005-userfunc.pg .. 1/? not ok 6 - Should now have one user # Failed test 6: "Should now have one user" # have: 0 # want: 1 # Looks like you failed 1 test of 6 tests/005-userfunc.pg .. Failed 1/6 subtests Test Summary Report ------------------- tests/005-userfunc.pg (Wstat: 0 Tests: 6 Failed: 1) Failed test: 6 Files=1, Tests=6, 1 wallclock secs Result: FAIL
  • 182. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS''; 005-userfunc.sql
  • 183. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT $$ ) RETURNS VOID LANGUAGE SQL AS INSERT INTO users values($1, $2); $$; 005-userfunc.sql
  • 184. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT $$ ) RETURNS VOID LANGUAGE SQL AS INSERT INTO users values($1, $2); $$; Bare minimum 005-userfunc.sql
  • 186. …To make ’em pass % psql -d flipr -f sql/005-userfunc.sql CREATE FUNCTION %
  • 187. …To make ’em pass % psql -d flipr -f sql/005-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/005-userfunc.pg tests/005-userfunc.pg .. ok All tests successful. Files=1, Tests=6, 0 wallclock secs Result: PASS
  • 188. …To make ’em pass % psql -d flipr -f sql/005-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/005-userfunc.pg tests/005-userfunc.pg .. ok All tests successful. Files=1, Tests=6, 0 wallclock secs Result: PASS Great!
  • 189. Add More Assertions 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; 006-userfunc.pg
  • 190. Add More Assertions 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'; 006-userfunc.pg
  • 191. Add More Assertions 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'; 006-userfunc.pg
  • 192. Add More Assertions 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'; 006-userfunc.pg
  • 194. Run ’Em % pg_prove -d flipr tests/006-userfunc.pg tests/006-userfunc.pg .. 1/? not ok 7 - Password should not be clear text # Failed test 7: "Password should not be clear text" # have: wet blanket # want: anything else # Looks like you failed 1 test of 7 tests/006-userfunc.pg .. Failed 1/7 subtests Test Summary Report ------------------- tests/006-userfunc.pg (Wstat: 0 Tests: 7 Failed: 1) Failed test: 7 Files=1, Tests=7, 0 wallclock secs Result: FAIL
  • 195. Run ’Em % pg_prove -d flipr tests/006-userfunc.pg tests/006-userfunc.pg .. 1/? not ok 7 - Password should not be clear text # Failed test 7: "Password should not be clear text" # have: wet blanket # want: anything else # Looks like you failed 1 test of 7 tests/006-userfunc.pg .. Failed 1/7 subtests Test Summary Report ------------------- tests/006-userfunc.pg (Wstat: 0 Tests: 7 Failed: 1) Failed test: 7 Files=1, Tests=7, 0 wallclock secs Result: FAIL
  • 196. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $ ); 2 $$; 006-userfunc.sql
  • 197. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $ ); 1 $$; 006-userfunc.sql
  • 198. Modify for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $ ); 1 $$; I’m not kidding 006-userfunc.sql
  • 200. …To make ’em pass % psql -d flipr -f sql/006-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/006-userfunc.pg tests/006-userfunc.pg .. ok All tests successful. Files=1, Tests=7, 1 wallclock secs Result: PASS
  • 201. …To make ’em pass % psql -d flipr -f sql/006-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/006-userfunc.pg tests/006-userfunc.pg .. ok All tests successful. Files=1, Tests=7, 1 wallclock secs Result: PASS So what?
  • 202. Sanity Check SELECT isnt( password, 'wet blanket', 'Password should not be clear text' ) FROM users WHERE nickname = 'theory'; 007-userfunc.pg
  • 203. Sanity Check 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'; 007-userfunc.pg
  • 204. Sanity Check 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'; 007-userfunc.pg
  • 205. Sanity Check 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'; 007-userfunc.pg
  • 207. Are We Insane? pg_prove -d flipr tests/007-userfunc.pg tests/007-userfunc.pg .. 1/? not ok 8 - Password should not be nickname # Failed test 8: "Password should not be nickname" # have: theory # want: anything else # Looks like you failed 1 test of 8 tests/007-userfunc.pg .. Failed 1/8 subtests Test Summary Report ------------------- tests/007-userfunc.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 0 wallclock secs Result: FAIL
  • 208. Are We Insane? pg_prove -d flipr tests/007-userfunc.pg tests/007-userfunc.pg .. 1/? not ok 8 - Password should not be nickname # Failed test 8: "Password should not be nickname" # have: theory # want: anything else # Looks like you failed 1 test of 8 tests/007-userfunc.pg .. Failed 1/8 subtests Test Summary Report ------------------- tests/007-userfunc.pg (Wstat: 0 Tests: 8 Failed: 1) Failed test: 8 Files=1, Tests=8, 0 wallclock secs Result: FAIL
  • 209. Change for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $1); $$; 007-userfunc.sql
  • 210. Change for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, 'whatever'); $$; 007-userfunc.sql
  • 211. Change for the Test CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, 'whatever'); $$; That’ll do it. 007-userfunc.sql
  • 213. See ’em Pass % psql -d flipr -f sql/007-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/007-userfunc.pg tests/007-userfunc.pg .. ok All tests successful. Files=1, Tests=8, 0 wallclock secs Result: PASS
  • 214. See ’em Pass % psql -d flipr -f sql/007-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/007-userfunc.pg tests/007-userfunc.pg .. ok All tests successful. Files=1, Tests=8, 0 wallclock secs Result: PASS Exciting, no?
  • 215. Add Another User… SELECT isnt( password, 'theory', 'Password should not be nickname' ) FROM users WHERE nickname = 'theory'; 008-userfunc.pg
  • 216. Add Another User… 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' ); 008-userfunc.pg
  • 217. Add Another User… 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' ); 008-userfunc.pg
  • 218. Add Another User… 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' ); 008-userfunc.pg
  • 219. Add Another User… 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' ); 008-userfunc.pg
  • 220. Let Us Be Inconsistent
  • 221. Let Us Be Inconsistent pg_prove -d flipr tests/008-userfunc.pg tests/008-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: whatever # want: anything else # Looks like you failed 1 test of 11 tests/008-userfunc.pg .. Failed 1/11 subtests Test Summary Report ------------------- tests/008-userfunc.pg (Wstat: 0 Tests: 11 Failed: 1) Failed test: 11 Files=1, Tests=11, 0 wallclock secs Result: FAIL
  • 222. Let Us Be Inconsistent pg_prove -d flipr tests/008-userfunc.pg tests/008-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: whatever # want: anything else # Looks like you failed 1 test of 11 tests/008-userfunc.pg .. Failed 1/11 subtests Test Summary Report ------------------- tests/008-userfunc.pg (Wstat: 0 Tests: 11 Failed: 1) Failed test: 11 Files=1, Tests=11, 0 wallclock secs Result: FAIL
  • 223. Cheat Death CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, 'whatever'); $$; 008-userfunc.sql
  • 224. Cheat Death CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $1 || $2); $$; 008-userfunc.sql
  • 225. Cheat Death CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $1 || $2); $$; No, seriously. 008-userfunc.sql
  • 227. Take a Pass % psql -d flipr -f sql/008-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/008-userfunc.pg tests/008-userfunc.pg .. ok All tests successful. Files=1, Tests=11, 0 wallclock secs Result: PASS
  • 228. Take a Pass % psql -d flipr -f sql/008-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/008-userfunc.pg tests/008-userfunc.pg .. ok All tests successful. Files=1, Tests=11, 0 wallclock secs Result: PASS Not unexpected.
  • 229. Let’s Get Serious SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); 009-userfunc.pg
  • 230. Let’s Get Serious 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; 009-userfunc.pg
  • 231. Let’s Get Serious 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; 009-userfunc.pg
  • 232. Let’s Get Serious 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; 009-userfunc.pg
  • 233. Let’s Get Serious 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; 009-userfunc.pg
  • 234. Let’s Get Serious 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; 009-userfunc.pg
  • 235.
  • 236. % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 12 - Password should not contain nickname # Failed test 12: "Password should not contain nickname" # 'theorywet blanket' # matches: '%theory%' not ok 13 - Password should not contain nickname # Failed test 13: "Password should not contain nickname" # 'strongrrlwet blanket' # matches: '%strongrrl%' not ok 14 - Password should not contain clear text # Failed test 14: "Password should not contain clear text" # 'theorywet blanket' # matches: '%wet blanket%' not ok 15 - Password should not contain clear text # Failed test 15: "Password should not contain clear text" # 'strongrrlwet blanket' # matches: '%wet blanket%' # Looks like you failed 4 tests of 15 tests/009-userfunc.pg .. Failed 4/15 subtests Test Summary Report ------------------- tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4) Failed tests: 12-15 Files=1, Tests=15, 0 wallclock secs Result: FAIL
  • 237. % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 12 - Password should not contain nickname # Failed test 12: "Password should not contain nickname" # 'theorywet blanket' # matches: '%theory%' not ok 13 - Password should not contain nickname # Failed test 13: "Password should not contain nickname" # 'strongrrlwet blanket' # matches: '%strongrrl%' not ok 14 - Password should not contain clear text # Failed test 14: "Password should not contain clear text" # 'theorywet blanket' # matches: '%wet blanket%' not ok 15 - Password should not contain clear text # Failed test 15: "Password should not contain clear text" # 'strongrrlwet blanket' # matches: '%wet blanket%' # Looks like you failed 4 tests of 15 tests/009-userfunc.pg .. Failed 4/15 subtests Test Summary Report ------------------- tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4) Failed tests: 12-15 Files=1, Tests=15, 0 wallclock secs Result: FAIL
  • 238. % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 12 - Password should not contain nickname # Failed test 12: "Password should not contain nickname" # 'theorywet blanket' # matches: '%theory%' not ok 13 - Password should not contain nickname # Failed test 13: "Password should not contain nickname" # 'strongrrlwet blanket' # matches: '%strongrrl%' not ok 14 - Password should not contain clear text # Failed test 14: "Password should not contain clear text" # 'theorywet blanket' # matches: '%wet blanket%' not ok 15 - Password should not contain clear text # Failed test 15: "Password should not contain clear text" # 'strongrrlwet blanket' # matches: '%wet blanket%' # Looks like you failed 4 tests of 15 tests/009-userfunc.pg .. Failed 4/15 subtests Test Summary Report ------------------- tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 4) Failed tests: 12-15 Files=1, Tests=15, 0 wallclock secs Result: FAIL
  • 239. Try MD5 CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, $1 || $2); $$; 009-userfunc.sql
  • 240. Try MD5 CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, md5($2)); $$; 009-userfunc.sql
  • 241. Try MD5 CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, md5($2)); $$; It’s hashish. 009-userfunc.sql
  • 243. Here We Go! % psql -d flipr sql/009-userfunc.sql % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: 3c908e08d874ad9ec39bffc93b5fb983 # want: anything else # Looks like you failed 1 test of 15 tests/009-userfunc.pg .. Failed 1/15 subtests Test Summary Report ------------------- tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 11 Files=1, Tests=15, 0 wallclock secs Result: FAIL
  • 244. Here We Go! % psql -d flipr sql/009-userfunc.sql % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: 3c908e08d874ad9ec39bffc93b5fb983 # want: anything else # Looks like you failed 1 test of 15 tests/009-userfunc.pg .. Failed 1/15 subtests Test Summary Report ------------------- Wait, what? tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 11 Files=1, Tests=15, 0 wallclock secs Result: FAIL
  • 245. Here We Go! % psql -d flipr sql/009-userfunc.sql % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. 1/? not ok 11 - Same password should not match # Failed test 11: "Same password should not match" # have: 3c908e08d874ad9ec39bffc93b5fb983 # want: anything else # Looks like you failed 1 test of 15 tests/009-userfunc.pg .. Failed 1/15 subtests Test Summary Report ------------------- Wait, what? tests/009-userfunc.pg (Wstat: 0 Tests: 15 Failed: 1) Failed test: 11 Files=1, Tests=15, 0 wallclock secs Result: FAIL
  • 246. Whaaaaa? 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; 009-userfunc.pg
  • 247. Whaaaaa? 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; 009-userfunc.pg
  • 248. Whaaaaa? 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' Regression! ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
  • 249. Whaaaaa? SELECT isnt( (SELECT password FROM users WHERE nickname = 'strongrrl'), (SELECT password FROM users WHERE nickname = 'theory'), 'Same password should not match' ); Must SELECT unalike( password, obscure '%' || nickname || '%', 'Password should not contain nickname' dupes. Regression! ) FROM users; SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 009-userfunc.pg
  • 251. Where Are We? Function adds user antisocial network
  • 252. Where Are We? Function adds user Nickname not in password antisocial network
  • 253. Where Are We? Function adds user Nickname not in password No clear text password antisocial network
  • 254. Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates antisocial network
  • 255. Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates How? antisocial network
  • 256. Where Are We? Function adds user Nickname not in password No clear text password Must obscure duplicates How? Solution: pg_crypto antisocial network
  • 257. Test for pg_crypto SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); 010-schema.pg
  • 258. Test for pg_crypto SELECT col_has_default( 'users', 'timestamp' ); SELECT col_default_is( 'users', 'timestamp', 'now()' ); SELECT has_function('crypt'); SELECT has_function('gen_salt'); 010-schema.pg
  • 260. See Test Fail % pg_prove -d flipr tests/010-schema.pg tests/010-schema.pg .. 1/? not ok 17 - Function crypt() should exist # Failed test 17: "Function crypt() should exist" not ok 18 - Function gen_salt() should exist # Failed test 18: "Function gen_salt() should exist" # Looks like you failed 2 tests of 18 tests/010-schema.pg .. Failed 2/18 subtests Test Summary Report ------------------- tests/010-schema.pg (Wstat: 0 Tests: 18 Failed: 2) Failed tests: 17-18 Files=1, Tests=18, 0 wallclock secs Result: FAIL
  • 262. Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql %
  • 263. Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql % pg_prove -d flipr tests/004-schema.pg tests/004-schema.pg .. ok All tests successful. Files=1, Tests=18, 1 wallclock secs Result: PASS
  • 264. Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql % pg_prove -d flipr tests/004-schema.pg tests/004-schema.pg .. ok All tests successful. Files=1, Tests=18, 1 wallclock secs Result: PASS Great.
  • 265. Install pg_crypto % export PG=/usr/local/pgsql % psql -d flipr -f $PG/share/contrib/pgcrypto.sql % pg_prove -d flipr tests/004-schema.pg tests/004-schema.pg .. ok All tests successful. Files=1, Tests=18, 1 wallclock secs Result: PASS Great. Let’s use them!
  • 266. Use pg_crypt CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, md5($2)); $$; 010-userfunc.sql
  • 267. Use pg_crypt CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, crypt($1, gen_salt('md5'))); $$; 010-userfunc.sql
  • 268. Use pg_crypt CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ INSERT INTO users values($1, crypt($1, gen_salt('md5'))); $$; Cryptonite! 010-userfunc.sql
  • 270. Cryptonomicon! % psql -d flipr -f sql/010-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. ok All tests successful. Files=1, Tests=15, 0 wallclock secs Result: PASS
  • 271. Cryptonomicon! % psql -d flipr -f sql/010-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/009-userfunc.pg tests/009-userfunc.pg .. ok All tests successful. Files=1, Tests=15, 0 wallclock secs Result: PASS Finally!
  • 272. Sanity Check SELECT unalike( password, '%wet blanket%', 'Password should not contain clear text' ) FROM users; 011-userfunc.pg
  • 273. Sanity Check 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; 011-userfunc.pg
  • 274. Sanity Check 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; 011-userfunc.pg
  • 275. Sanity Check 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; 011-userfunc.pg
  • 277. Are We Sane? % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. 1/? not ok 16 - Password should match crypted password # Failed test 16: "Password should match crypted password" # have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK. # want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0 not ok 17 - Password should match crypted password # Failed test 17: "Password should match crypted password" # have: $1$KCxoeJAK$APSOlalADk1akGrItOASx. # want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0 # Looks like you failed 2 tests of 17 tests/011-userfunc.pg .. Failed 2/17 subtests Test Summary Report ------------------- tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2) Failed tests: 16-17 Files=1, Tests=17, 0 wallclock secs Result: FAIL
  • 278. Are We Sane? % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. 1/? not ok 16 - Password should match crypted password # Failed test 16: "Password should match crypted password" # have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK. # want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0 not ok 17 - Password should match crypted password # Failed test 17: "Password should match crypted password" # have: $1$KCxoeJAK$APSOlalADk1akGrItOASx. # want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0 # Looks like you failed 2 tests of 17 tests/011-userfunc.pg .. Failed 2/17 subtests Test Summary Report ------------------- Say what? tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2) Failed tests: 16-17 Files=1, Tests=17, 0 wallclock secs Result: FAIL
  • 279. Are We Sane? % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. 1/? not ok 16 - Password should match crypted password # Failed test 16: "Password should match crypted password" # have: $1$PmBl0CzG$sUwGMzxCKqovvMKaBhyXK. # want: $1$PmBl0CzG$E/uNgzObrQHl3Wp.C6/TX0 not ok 17 - Password should match crypted password # Failed test 17: "Password should match crypted password" # have: $1$KCxoeJAK$APSOlalADk1akGrItOASx. # want: $1$KCxoeJAK$DrhZ5wT4aUzJE.RydnJLy0 # Looks like you failed 2 tests of 17 tests/011-userfunc.pg .. Failed 2/17 subtests Test Summary Report ------------------- Say what? tests/011-userfunc.pg (Wstat: 0 Tests: 17 Failed: 2) Failed tests: 16-17 Files=1, Tests=17, 0 wallclock secs Result: FAIL
  • 280. What’d We Do Wrong? CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ 1 INSERT INTO users values($1, crypt($ , gen_salt('md5'))); $$; 011-userfunc.sql
  • 281. What’d We Do Wrong? CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ 2 INSERT INTO users values($1, crypt($ , gen_salt('md5'))); $$; 011-userfunc.sql
  • 282. What’d We Do Wrong? CREATE OR REPLACE FUNCTION ins_user( nickname TEXT, password TEXT ) RETURNS VOID LANGUAGE SQL AS $$ 2 INSERT INTO users values($1, crypt($ , gen_salt('md5'))); $$; Cryptonite! 011-userfunc.sql
  • 283. And…
  • 284. And… % psql -d flipr -f sql/011-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. ok All tests successful. Files=1, Tests=17, 1 wallclock secs Result: PASS
  • 285. And… % psql -d flipr -f sql/011-userfunc.sql CREATE FUNCTION % pg_prove -d flipr tests/011-userfunc.pg tests/011-userfunc.pg .. ok All tests successful. Files=1, Tests=17, 1 wallclock secs Result: PASS We’re there!
  • 287. Next: upd_pass() Need password updating antisocial network
  • 288. Next: upd_pass() Need password updating Things to consider: antisocial network
  • 289. Next: upd_pass() Need password updating Things to consider: Update nonexistent user? antisocial network
  • 290. Next: upd_pass() Need password updating Things to consider: Update nonexistent user? Update with invalid password? antisocial network
  • 291. Next: upd_pass() Need password updating Things to consider: Update nonexistent user? Update with invalid password? Was password updated? antisocial network
  • 292. antisocial network Your Turn
  • 293. antisocial network Your Turn Create upd_pass()
  • 294. antisocial network Your Turn Create upd_pass() Project Repo: http://github.com/theory/tddd/
  • 296. How’d We Do? 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'); 012-userfunc.sql
  • 297. How’d We Do? 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'); 012-userfunc.sql
  • 298. How’d We Do? 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'); 012-userfunc.sql
  • 299. How’d We Do? 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'); 012-userfunc.sql
  • 300. How’d We Do? 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'); 012-userfunc.sql
  • 301. How’d We Do? 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'); 012-userfunc.sql
  • 302. How’d We Do? 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'; 012-userfunc.sql
  • 303. How’d We Do? 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'; 012-userfunc.sql
  • 304. How’d We Do? 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'; 012-userfunc.sql
  • 305. How’d We Do? 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'; 012-userfunc.sql
  • 307. How’d We Do? 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'; 012-userfunc.sql
  • 308. How’d We Do? 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'; 012-userfunc.sql
  • 309. How’d We Do? 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'; 012-userfunc.sql
  • 310. What Does it Look Like? 013-privs.sql
  • 311. What Does it Look Like? CREATE OR REPLACE FUNCTION upd_pass( nick TEXT, oldpass TEXT, newpass TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql AS $$ BEGIN UPDATE users SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; 013-privs.sql
  • 312. What Does it Look Like? CREATE OR REPLACE FUNCTION upd_pass( nick TEXT, oldpass TEXT, newpass TEXT ) RETURNS BOOLEAN LANGUAGE plpgsql AS $$ BEGIN UPDATE users SET password = crypt($3, gen_salt('md5')) WHERE nickname = $1 AND password = crypt($2, password); RETURN FOUND; END; $$; 013-privs.sql Good, eh?
  • 314. What About Privileges? Got solid functions antisocial network
  • 315. What About Privileges? Got solid functions Apps still using tables antisocial network
  • 316. What About Privileges? Got solid functions Apps still using tables How to prevent that? antisocial network
  • 317. What About Privileges? Got solid functions Apps still using tables How to prevent that? Privileges! antisocial network
  • 318. What About Privileges? Got solid functions Apps still using tables How to prevent that? Privileges! Deny access to table antisocial network
  • 319. What About Privileges? Got solid functions Apps still using tables How to prevent that? Privileges! Deny access to table Allow access to functions antisocial network
  • 321. Test Privileges SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT finish(); ROLLBACK; 013-privs.pg
  • 322. Test Privileges SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT finish(); ROLLBACK; 013-privs.pg
  • 323. Test Privileges SET search_path = public,tap; BEGIN; --SELECT plan( 5 ); SELECT * FROM no_plan(); SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT finish(); ROLLBACK; 013-privs.pg
  • 325. Check Privileges % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. 1/? not ok 1 - Role fliprapp should exist # Failed test 1: "Role fliprapp should exist" not ok 2 - User fliprapp should not be a super user # Failed test 2: "User fliprapp should not be a super user" # User fliprapp does not exist # Looks like you failed 2 tests of 2 tests/013-privs.pg .. Failed 2/2 subtests Test Summary Report ------------------- tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2) Failed tests: 1-2 Files=1, Tests=2, 0 wallclock secs Result: FAIL
  • 326. Check Privileges % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. 1/? not ok 1 - Role fliprapp should exist # Failed test 1: "Role fliprapp should exist" not ok 2 - User fliprapp should not be a super user # Failed test 2: "User fliprapp should not be a super user" # User fliprapp does not exist # Looks like you failed 2 tests of 2 tests/013-privs.pg .. Failed 2/2 subtests Test Summary Report ------------------- tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2) Failed tests: 1-2 Files=1, Tests=2, 0 wallclock secs Result: FAIL
  • 327. Check Privileges % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. 1/? not ok 1 - Role fliprapp should exist # Failed test 1: "Role fliprapp should exist" not ok 2 - User fliprapp should not be a super user # Failed test 2: "User fliprapp should not be a super user" # User fliprapp does not exist # Looks like you failed 2 tests of 2 tests/013-privs.pg .. Failed 2/2 subtests Test Summary Report ------------------- tests/013-privs.pg (Wstat: 0 Tests: 2 Failed: 2) Failed tests: 1-2 Files=1, Tests=2, 0 wallclock secs Result: FAIL
  • 329. Add a User CREATE USER fliprapp WITH LOGIN; 013-privs.sql
  • 330. Add a User CREATE USER fliprapp WITH LOGIN; Easy, right? 013-privs.sql
  • 332. User Test % psql -d flipr -f sql/013-privs.sql CREATE ROLE % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. ok All tests successful. Files=1, Tests=2, 0 wallclock secs Result: PASS
  • 333. User Test % psql -d flipr -f sql/013-privs.sql CREATE ROLE % pg_prove -d flipr tests/013-privs.pg tests/013-privs.pg .. ok All tests successful. Files=1, Tests=2, 0 wallclock secs Result: PASS Riiiiight.
  • 334. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); 014-userfunc.pg
  • 335. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT volatility_is('ins_user', 'volatile'); SELECT volatility_is('upd_pass', 'volatile'); SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 014-userfunc.pg
  • 336. Sanity Check SELECT has_role('fliprapp'); SELECT isnt_superuser('fliprapp'); SELECT volatility_is('ins_user', 'volatile'); SELECT volatility_is('upd_pass', 'volatile'); SELECT is_definer('ins_user'); SELECT is_definer('upd_pass'); 014-userfunc.pg

Notas do Editor

  1. TODO: • Add more privilege stuff? • Add example of renaming `flips.timestamp`?
  2. TODO: • Add more privilege stuff? • Add example of renaming `flips.timestamp`?
  3. TODO: • Add more privilege stuff? • Add example of renaming `flips.timestamp`?
  4. TODO: • Add more privilege stuff? • Add example of renaming `flips.timestamp`?