in reply to STDIN typeglob

G'day Bod,

"... reads JSON data from STDIN on a webserver. ... I have had to simulate this."

There's a number of ways a program can read from STDIN:

# By default $ cat > TabNL Tab NL # By redirection $ cat -vet < TabNL Tab^INL$ # By piping $ cat TabNL | cat -vet Tab^INL$ # By using '-' as a special filename $ cat -vet - Tab NL Tab^INL$ # By others I didn't immediately think of

Which of those methods does your module use? Knowing this will allow us to better advise you on ways to perform the simulation. :-)

"Using code I found in an answer on SO ... *STDIN = *DATA; ..."

That's not really simulating STDIN. You're just rebadging an existing filehandle:

$ perl -E ' use strict; use warnings; say "Real \\*STDIN fileno: ", fileno(\*STDIN); say "Real \\*DATA fileno: ", fileno(\*DATA); *STDIN = *DATA; say "Fake \\*STDIN fileno: ", fileno(\*STDIN); __DATA__ some data ' Real \*STDIN fileno: 0 Real \*DATA fileno: 3 Fake \*STDIN fileno: 3

Beyond simulating the input, it would probably help to have some idea of what tests you intend to run.

Here's a test script that simulates JSON being piped to your application. It's subsequently decoded and compared with reference data via is_deeply() (presumably you'd have more useful tests here). Note how you can run the tests on multiple JSON files.

ken@titan ~/tmp/pm_11152777_test_stdin/t $ cat test_json.t #!perl use strict; use warnings; use autodie; use Cwd 'abs_path'; use File::Basename 'dirname'; my $THISDIR; BEGIN { $THISDIR = dirname abs_path __FILE__ } use JSON::MaybeXS; use POSIX '_exit'; use Test::More; my @file_bases = qw{test1 testA}; plan tests => 0+@file_bases; for my $file_base (@file_bases) { my $json_data = ''; my $child_pid = open my $from_kid, '-|'; if ($child_pid) { # parent process (pipe from child): # reads JSON from "effective" STDIN while (my $line = <$from_kid>) { $json_data .= $line; } waitpid $child_pid, 0; } else { # child process (pipe to parent): # writes JSON to STDOUT my $json_file = "$THISDIR/../data/$file_base.json"; open my $json_fh, '<', $json_file; while (my $line = <$json_fh>) { print $line; } _exit 0; } my $perl_data = decode_json($json_data); my $reference_data = do "$THISDIR/../data/$file_base.perl"; is_deeply $perl_data, $reference_data, "Testing '$file_base'"; }

Here's the test data:

ken@titan ~/tmp/pm_11152777_test_stdin/data $ cat test1.json { "key1" : "val1", "key2" : [ "elem1", "elem2", "elem3" ], "key3" : { "name1" : "value1", "name2" : "value2" } } $ cat test1.perl { key1 => 'val1', key2 => [qw{elem1 elem2 elem3}], key3 => {name1 => 'value1', name2 => 'value2'}, }; $ cat testA.json { "keyA" : "valA", "keyB" : [ "elemA", "elemB", "elemC" ], "keyC" : { "nameA" : "valueA", "nameB" : "valueB" } } $ cat testA.perl { keyA => 'valA', keyB => [qw{elemA elemB elemC}], keyC => {nameA => 'valueA', nameB => 'valueB'}, };

And here's an actual test run:

ken@titan ~/tmp/pm_11152777_test_stdin $ prove -v t/test_json.t t/test_json.t .. 1..2 ok 1 - Testing 'test1' ok 2 - Testing 'testA' ok All tests successful. Files=1, Tests=2, 1 wallclock secs ( 0.01 usr 0.03 sys + 0.12 cusr + 0.08 csys = 0.25 CPU) Result: PASS

— Ken

Replies are listed 'Best First'.
Re^2: STDIN typeglob
by Bod (Parson) on Jun 13, 2023 at 21:06 UTC
    it would probably help to have some idea of what tests you intend to run

    Thanks kcott,

    eyepopslikeamosquito identified the new method in this comment - Re^3: STDIN typeglob

    The test I am trying to run is like this only with a bigger JSON object...

    #!perl use 5.006; use strict; use warnings; use Test::More; use Business::Stripe::Webhook; plan tests => 7; *STDIN = *DATA; my $webhook_fail = Business::Stripe::Webhook->new( 'signing_secret' => 'whsec_...', 'invoice-paid' => \&pay_invoice, ); ok( !$webhook_fail->success, "Didn't instantiate" ); is( $webhook_fail->error, "Looks like this is not a web request!", "No +t a web request" ); # Pretend we are on a webserver $ENV{'GATEWAY_INTERFACE'} = 'CGI/1.1'; $ENV{'CONTENT_LENGTH'} = 10024; $ENV{'HTTP_STRIPE_SIGNATURE'} = 't=ABCDEFGHIJ,v1=abcdefghij'; my $webhook_pass1 = Business::Stripe::Webhook->new( 'invoice-paid' => \&pay_invoice, ); ok( $webhook_pass1->success, "Basic instantiation" ); $webhook_pass1->process(); my $webhook_fail2 = Business::Stripe::Webhook->new( signing_secret => 'whsec_...', 'invoice-paid' => \&pay_invoice, ); is( $webhook_fail2->error, 'No payload data', "No payload for signed i +nstantiation" ); $webhook_fail2->process(); ok( !$webhook_fail2->success, "Signature error" ); is( $webhook_fail2->error, 'Invalid Stripe Signature', "Invalid signat +ure" ); sub pay_invoice { is( $_[0]->{'object'}, 'event', "pay.invoice handled" ); } __DATA__ { "id": "evt_1NFK32EfkkexSbWLZb6LoEap", "object": "event", "api_version": "2020-08-27", "data": { "object": { "id": "in_1NFK30EfkkfpSbWLeMoI8HzB", } } }

Re^2: STDIN typeglob
by Bod (Parson) on Jun 18, 2023 at 17:10 UTC
    Which of those methods does your module use?

    I reads STDIN like this...

    read(STDIN, $vars{'payload'}, $ENV{'CONTENT_LENGTH'});

    But following advice given elsewhere, that is deprecated now and it is up to the user to read STDIN or wherever else they want to get the data from. They then pass that to the constructor. That is still there as a fallback - for now...

      I reads STDIN [...] but following advice given elsewhere, that is deprecated now [...] That is still there as a fallback - for now...

      Why do you keep that around? Has your module already attracted a relevant number of users? If so, that's nice (stable API), and you should IMHO document a date after which you will remove reading STDIN. If not, just drop it completely.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
        Has your module already attracted a relevant number of users?

        Other than the number of people who like it on CPAN, how would I know?