SlideShare una empresa de 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

Más contenido relacionado

La actualidad más candente

OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話Daichi Koike
 
スマホ(Android・iPhone)でWebRTC
スマホ(Android・iPhone)でWebRTCスマホ(Android・iPhone)でWebRTC
スマホ(Android・iPhone)でWebRTCNatsuki Yamanaka
 
PHP Powerpoint -- Teach PHP with this
PHP Powerpoint -- Teach PHP with thisPHP Powerpoint -- Teach PHP with this
PHP Powerpoint -- Teach PHP with thisIan Macali
 
ログ解析基盤におけるストリーム処理パイプラインについて
ログ解析基盤におけるストリーム処理パイプラインについてログ解析基盤におけるストリーム処理パイプラインについて
ログ解析基盤におけるストリーム処理パイプラインについてcyberagent
 
中小規模サービスのApacheチューニング
中小規模サービスのApacheチューニング中小規模サービスのApacheチューニング
中小規模サービスのApacheチューニング勲 國府田
 
テスト用ライブラリ power-assert
テスト用ライブラリ power-assertテスト用ライブラリ power-assert
テスト用ライブラリ power-assertTakuto Wada
 
Ruby on Rails Presentation
Ruby on Rails PresentationRuby on Rails Presentation
Ruby on Rails Presentationadamcookeuk
 
Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話KEISUKE KONISHI
 
Robot framework
Robot frameworkRobot framework
Robot frameworkboriau
 
MongoDB + Java + Spring Data
MongoDB + Java + Spring DataMongoDB + Java + Spring Data
MongoDB + Java + Spring DataAnton Sulzhenko
 
Rediscovering Spring with Spring Boot(1)
Rediscovering Spring with Spring Boot(1)Rediscovering Spring with Spring Boot(1)
Rediscovering Spring with Spring Boot(1)Gunith Devasurendra
 
What's new in Spring Boot 2.6 ?
What's new in Spring Boot 2.6 ?What's new in Spring Boot 2.6 ?
What's new in Spring Boot 2.6 ?土岐 孝平
 
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くしたNginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くしたtoshi_pp
 
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -onozaty
 
AOT and Native with Spring Boot 3.0
AOT and Native with Spring Boot 3.0AOT and Native with Spring Boot 3.0
AOT and Native with Spring Boot 3.0MoritzHalbritter
 
[DI08] その情報うまく取り出せていますか? ~ 意外と簡単、Azure Search で短時間で検索精度と利便性を向上させるための方法
[DI08] その情報うまく取り出せていますか? ~ 意外と簡単、Azure Search で短時間で検索精度と利便性を向上させるための方法[DI08] その情報うまく取り出せていますか? ~ 意外と簡単、Azure Search で短時間で検索精度と利便性を向上させるための方法
[DI08] その情報うまく取り出せていますか? ~ 意外と簡単、Azure Search で短時間で検索精度と利便性を向上させるための方法de:code 2017
 

La actualidad más candente (20)

OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
OpenAPI 3.0でmicroserviceのAPI定義を試みてハマった話
 
スマホ(Android・iPhone)でWebRTC
スマホ(Android・iPhone)でWebRTCスマホ(Android・iPhone)でWebRTC
スマホ(Android・iPhone)でWebRTC
 
Jdbc_ravi_2016
Jdbc_ravi_2016Jdbc_ravi_2016
Jdbc_ravi_2016
 
PHP Powerpoint -- Teach PHP with this
PHP Powerpoint -- Teach PHP with thisPHP Powerpoint -- Teach PHP with this
PHP Powerpoint -- Teach PHP with this
 
ログ解析基盤におけるストリーム処理パイプラインについて
ログ解析基盤におけるストリーム処理パイプラインについてログ解析基盤におけるストリーム処理パイプラインについて
ログ解析基盤におけるストリーム処理パイプラインについて
 
中小規模サービスのApacheチューニング
中小規模サービスのApacheチューニング中小規模サービスのApacheチューニング
中小規模サービスのApacheチューニング
 
Java script ppt
Java script pptJava script ppt
Java script ppt
 
テスト用ライブラリ power-assert
テスト用ライブラリ power-assertテスト用ライブラリ power-assert
テスト用ライブラリ power-assert
 
Spring Boot
Spring BootSpring Boot
Spring Boot
 
Ruby on Rails Presentation
Ruby on Rails PresentationRuby on Rails Presentation
Ruby on Rails Presentation
 
Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話Swaggerでのapi開発よもやま話
Swaggerでのapi開発よもやま話
 
Robot framework
Robot frameworkRobot framework
Robot framework
 
At least onceってぶっちゃけ問題の先送りだったよね #kafkajp
At least onceってぶっちゃけ問題の先送りだったよね #kafkajpAt least onceってぶっちゃけ問題の先送りだったよね #kafkajp
At least onceってぶっちゃけ問題の先送りだったよね #kafkajp
 
MongoDB + Java + Spring Data
MongoDB + Java + Spring DataMongoDB + Java + Spring Data
MongoDB + Java + Spring Data
 
Rediscovering Spring with Spring Boot(1)
Rediscovering Spring with Spring Boot(1)Rediscovering Spring with Spring Boot(1)
Rediscovering Spring with Spring Boot(1)
 
What's new in Spring Boot 2.6 ?
What's new in Spring Boot 2.6 ?What's new in Spring Boot 2.6 ?
What's new in Spring Boot 2.6 ?
 
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くしたNginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
 
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
 
AOT and Native with Spring Boot 3.0
AOT and Native with Spring Boot 3.0AOT and Native with Spring Boot 3.0
AOT and Native with Spring Boot 3.0
 
[DI08] その情報うまく取り出せていますか? ~ 意外と簡単、Azure Search で短時間で検索精度と利便性を向上させるための方法
[DI08] その情報うまく取り出せていますか? ~ 意外と簡単、Azure Search で短時間で検索精度と利便性を向上させるための方法[DI08] その情報うまく取り出せていますか? ~ 意外と簡単、Azure Search で短時間で検索精度と利便性を向上させるための方法
[DI08] その情報うまく取り出せていますか? ~ 意外と簡単、Azure Search で短時間で検索精度と利便性を向上させるための方法
 

Similar 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
 

Similar 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
 

Último

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
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 

Último (20)

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
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 

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 del 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`?