The Trunk: WebClient-Tests-topa.48.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

The Trunk: WebClient-Tests-topa.48.mcz

commits-2
Tobias Pape uploaded a new version of WebClient-Tests to project The Trunk:
http://source.squeak.org/trunk/WebClient-Tests-topa.48.mcz

==================== Summary ====================

Name: WebClient-Tests-topa.48
Author: topa
Time: 20 April 2015, 2:50:36.764 pm
UUID: 031c09ee-026e-48c3-ba4d-f9d9fd4e2048
Ancestors: WebClient-Tests-fbs.47

Minor test refactoring

==================== Snapshot ====================

SystemOrganization addCategory: #'WebClient-Tests'!

TestCase subclass: #WebClientServerTest
        instanceVariableNames: 'server user password oldAuthHandler oldProxyHandler port'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'WebClient-Tests'!

!WebClientServerTest commentStamp: 'ar 2/24/2010 00:13' prior: 0!
Tests for both WebClient and WebServer.!

----- Method: WebClientServerTest>>decode: (in category 'tests - json') -----
decode: aString
        "Decodes the given string"
       
        ^WebUtils jsonDecode: aString readStream!

----- Method: WebClientServerTest>>encode: (in category 'tests - json') -----
encode: anObject
        "Encodes the given object"
       
        ^WebUtils jsonEncode: anObject!

----- Method: WebClientServerTest>>expectedFailures (in category 'setup') -----
expectedFailures
        "Some old versions of HTTPSocket are broken"

        ((HTTPSocket respondsTo: #httpRequestHandler:)
                or:[SystemVersion current version beginsWith: 'Pharo']) ifFalse:[^#(testMultipartFiles2)].

        ^#()!

----- Method: WebClientServerTest>>localHostUrl (in category 'setup') -----
localHostUrl
        ^'<a href="http://localhost:'">http://localhost:', self port asString!

----- Method: WebClientServerTest>>oAuthParams (in category 'tests - oauth') -----
oAuthParams
        "The base parameter set for oauth related tests.
        Example values taken from
                http://oauth.googlecode.com/svn/code/javascript/example/signature.html
        "

        ^Dictionary newFromPairs: {
                "Consumer key and secret"
                'oauth_consumer_key'. 'abcd'.
                'oauth_consumer_secret'. 'efgh'.

                "Token key and secret"
                'oauth_token'. 'ijkl'.
                'oauth_token_secret'. 'mnop'.
               
                'oauth_timestamp'. '1281668113'.
                'oauth_nonce'. 'FWNkVaRJVzE'.
               
                "Twitter uses oauth 1.0 with HMAC-SHA1"
                'oauth_version'. '1.0'.
                'oauth_signature_method'. 'HMAC-SHA1'.
        }.!

----- Method: WebClientServerTest>>port (in category 'setup') -----
port
        "Use a random port to minimise chances of concurrently running test suites clashing."
        ^ port
                ifNil: [port := (10000 to: 50000) atRandom]!

----- Method: WebClientServerTest>>setUp (in category 'setup') -----
setUp
        server := WebServer new listenOn: self port.
        server passwordAt: 'user' realm: 'test' put: 'pass'.
!

----- Method: WebClientServerTest>>tearDown (in category 'setup') -----
tearDown
        server ifNotNil:[server destroy].
!

----- Method: WebClientServerTest>>testArrays (in category 'tests - json') -----
testArrays
        "Test array encodings"

        self assert: (self decode: '[]') = #().
        self assert: (self decode: '[[]]') = #(#()).
        self assert: (self decode: '[[], []]') = #(#() #()).
        self assert: (self decode: '["hello", "world", 123]') = #('hello' 'world' 123).
        self assert: (self decode: '[["true", false, null]]') = #(('true' false nil)).

        self assert: (self encode: #()) =  '[]'.
        self assert: (self encode: #(#())) = '[[]]'.
        self assert: (self encode: #(#() #()) ) = '[[], []]'.
        self assert: (self encode: #('hello' 'world' 123)) = '["hello", "world", 123]'.
        self assert: (self encode: #(('true' false nil))) = '[["true", false, null]]'.

        self should: [self decode: '['] raise: Error.
        self should: [self decode: '[}'] raise: Error.
        self should: [self decode: '{[}'] raise: Error.
        self should: [self decode: '[[[]]'] raise: Error.
!

----- Method: WebClientServerTest>>testAuthException (in category 'tests - auth') -----
testAuthException
        "Test client and server handling of digest auth"

        | resp client |
        server addService: '/test/auth' action:[:req |
                server authenticate: req realm: 'test' methods: #(digest) do:[
                        req send200Response: 'ok'
                ].
        ].

        client := WebClient new.
        self should:[client httpGet: self localHostUrl, '/test/auth']
                raise: WebAuthRequired.

        client allowAuth: false.
        self shouldnt:[resp := client httpGet: self localHostUrl, '/test/auth']
                raise: WebAuthRequired.
        self assert: resp code = 401.
!

----- Method: WebClientServerTest>>testAuthRedirectSession (in category 'tests - redirect') -----
testAuthRedirectSession
        "Run a test for a full auth-redirect-cookie loop"

        | loginOK finalUrl finalFields firstRedirect client resp |
        server addService: '/login' action:[:req |
                server authenticate: req realm: 'test' methods: #(digest) do:[ | id |
                        loginOK := true.
                        server sessionAt: (id := UUID new hex) put: ''.
                        req send302Response: (req fields at: 'url' ifAbsent:['/'])
                                do:[:reply| reply setCookie: 'session' value: id path: '/']]].
        server addService: '/action' action:[:req |
                (server sessionAt: (req cookieAt: 'session')) ifNil:[
                        firstRedirect := true.
                        req send302Response: '/login?url=', req rawUrl encodeForHTTP.
                ] ifNotNil:[
                        finalUrl := req url.
                        finalFields := req fields.
                        req send200Response: 'ok'.
                ].
        ].
        firstRedirect := loginOK := false.
        server passwordAt: 'squeak' realm: 'test' put: 'foo'.
        client := WebClient new.
        client username: 'squeak'; password: 'foo'.
        resp := client httpGet: self localHostUrl, '/action/foo/bar?string=hello&number=42'.

        self assert: resp code = 200.
        self assert: firstRedirect.
        self assert: loginOK.
        self assert: finalUrl = '/action/foo/bar'.
        self assert: finalFields size = 2.
        self assert: (finalFields at: 'string') = 'hello'.
        self assert: (finalFields at: 'number') = '42'.
!

----- Method: WebClientServerTest>>testBasicAuth (in category 'tests - auth') -----
testBasicAuth
        "Test client and server basic auth"

        | resp reqHeader |
        server addService: '/test/auth' action:[:req |
                server authenticate: req realm: 'test' methods: #(basic) do:[
                        reqHeader := req headerAt: 'Authorization'.
                        req send200Response: 'ok'
                ].
        ].
        resp := WebClient new httpGet: self localHostUrl, '/test/auth'.
        self assert: resp code = 401.

        [resp := WebClient httpGet: self localHostUrl, '/test/auth']
                on: WebAuthRequired do:[:ex| ex username: 'squeak' password: 'squeak'].
        self assert: resp code = 401.

        [resp := WebClient httpGet: self localHostUrl, '/test/auth']
                on: WebAuthRequired do:[:ex| ex username: 'user' password: 'pass'].
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: (reqHeader beginsWith: 'Basic ').
!

----- Method: WebClientServerTest>>testChunkedLoopback (in category 'tests - chunked') -----
testChunkedLoopback
        "Test HTTP loopback streaming using chunked transfer-encoding"

        | queue response |
        queue := SharedQueue new.
        server addService: '/recv' action:[:req |

                "The /recv service establishes the write-end for the server.
                In a real environment we would access protect the request
                and also pass a token to be used to link the incoming /send
                request from the client."

                req sendResponse: 200 chunked:[:writeEnd|
                        | chunk |
                        "There is no reason to wait for the client to send a request,
                        the protocol is entirely freestyle. Send something just because
                        we can"
                        writeEnd nextChunkPut: 'Initial response'.
                        "And from here on echo any incoming data"
                        [chunk := queue next.
                        chunk == nil] whileFalse:[writeEnd nextChunkPut: chunk].
                        "And some final data"
                        writeEnd nextChunkPut: 'Final response'.
                ].
        ] methods: #('GET'). "only allow GET requests"

        server addService: '/send' action:[:req |

                "The /send service establishes the read-end for the server.
                Simply read the chunks as they come in and stick them in
                our loopback queue to send them back to the client."

                | chunk |
                [chunk := req nextChunk.
                chunk == nil] whileFalse:[queue nextPut: chunk].
                queue nextPut: nil. "end conversation"
                req send200Response: 'ok'.
        ] methods: #('POST'). "only allow POST requests"

        "Establish the server response stream"
        response := WebClient new httpGet: self localHostUrl, '/recv'.
        self assert: response code = 200.
        self assert: (response headerAt: 'Transfer-Encoding') = 'chunked'.
        self assert: response nextChunk equals: 'Initial response'.

        "Establish the client request stream"
        WebClient
                httpPostChunked: self localHostUrl, '/send'
                content:[:request|
                        "We've set up both ends, try our loopback server"
                        request nextChunkPut: 'Hello World'.
                        self assert: response nextChunk equals: 'Hello World'.
                        request nextChunkPut: 'The answer is 42'.
                        self assert: response nextChunk equals: 'The answer is 42'.
                ] type: nil.

        self assert: response nextChunk equals: 'Final response'.
        self assert: response nextChunk equals: nil.
!

----- Method: WebClientServerTest>>testChunkedRequest (in category 'tests - chunked') -----
testChunkedRequest
        "Test HTTP post using chunked transfer-encoding"

        | resp request |
        server addService: '/test' action:[:req |
                request := req.
                req send200Response: req content].

        resp := WebClient
                                httpPostChunked: self localHostUrl, '/test'
                                content:[:req|
                                        req nextChunkPut: 'Hello'.
                                        req nextChunkPut: 'World'.
                                        req nextChunkPut: 'Dude'.
                                ] type: nil.

        self assert: resp code = 200.
        self assert: resp content = 'HelloWorldDude'.
        self assert: (request headerAt: 'Transfer-Encoding') = 'chunked'!

----- Method: WebClientServerTest>>testChunkedResponse (in category 'tests - chunked') -----
testChunkedResponse
        "Test HTTP response using chunked transfer-encoding"

        | resp |
        server addService: '/test' action:[:req |
                req sendResponse: 200 chunked:[:response|
                        response nextChunkPut: 'Hello'.
                        response nextChunkPut: 'World'.
                        response nextChunkPut: 'Dude'.
                ].
        ].

        resp := WebClient httpGet: self localHostUrl, '/test'.
        self assert: resp code = 200.
        self assert: resp content = 'HelloWorldDude'.
        self assert: (resp headerAt: 'Transfer-Encoding') = 'chunked'.!

----- Method: WebClientServerTest>>testCookieDomainRules (in category 'tests - cookies') -----
testCookieDomainRules
        "Test cookie domain rules"

        "Request host matches domain"
        self assert: (WebClient new
                        acceptCookie: (WebCookie new domain: 'www.domain.com')
                        host: 'www.domain.com'
                        path: '/').

        "Request host matches subdomain"
        self assert: (WebClient new
                        acceptCookie: (WebCookie new domain: '.domain.com')
                        host: 'www.domain.com'
                        path: '/').

        "Request host does not match domain"
        self deny: (WebClient new
                        acceptCookie: (WebCookie new domain: 'www.domain.com')
                        host: 'ftp.domain.com'
                        path: '/').

        "Request host does not match subdomain"
        self deny: (WebClient new
                        acceptCookie: (WebCookie new domain: '.domain.com')
                        host: 'www.someother.org'
                        path: '/').

        "Request host has an extra subdomain"
        self deny: (WebClient new
                        acceptCookie: (WebCookie new domain: '.domain.com')
                        host: 'foo.bar.domain.com'
                        path: '/').
!

----- Method: WebClientServerTest>>testCookieExpiryParsing (in category 'tests - cookies') -----
testCookieExpiryParsing
        "Test parsing of cookie expiry dates"

        | exp a b |
        exp := DateAndTime date: Date today time: Time now.
        a := WebCookie new.
        a name: 'test'.
        a expiry: exp.
        b := WebCookie new readFrom: a asString readStream.
        self assert: b expiry = exp asUTC.

        b := WebCookie new readFrom: (a asString copyReplaceAll: '-' with: ' ') readStream.
        self assert: b expiry = exp asUTC.
!

----- Method: WebClientServerTest>>testCookieParsing (in category 'tests - cookies') -----
testCookieParsing
        "Test client and server handling of cookies"

        | resp client |
        "Sends the testcookie back if it's set"
        server addService: '/' action:[:req |
                req send200Response:(String streamContents:[:s|
                        req cookiesDo:[:key :val| s nextPutAll: key, '=', val; cr].
                ]).
        ].

        client := WebClient new.
        resp := client httpGet: self localHostUrl,'/cookieA' do:[:req|
                req headerAt: 'Cookie' put: 'foo=bar; key=value, x=32, y=55'.
        ].
        resp content; close.
        self assert: resp code = 200.
        self assert: resp content
                equals: 'foo=bar', String cr,
                                'key=value', String cr,
                                'x=32', String cr,
                                'y=55', String cr.

        client := WebClient new.
        resp := client httpGet: self localHostUrl,'/cookieA' do:[:req|
                req addHeader: 'Cookie' value: 'foo=bar'.
                req addHeader: 'Cookie' value: 'key=value'.
                req addHeader: 'Cookie' value: 'x=32'.
                req addHeader: 'Cookie' value: 'y=55'.
        ].
        resp content; close.
        self assert: resp code = 200.
        self assert: resp content
                equals: 'foo=bar', String cr,
                                'key=value', String cr,
                                'x=32', String cr,
                                'y=55', String cr.
!

----- Method: WebClientServerTest>>testCookies (in category 'tests - cookies') -----
testCookies
        "Test client and server handling of cookies"

        | resp client |
        "Sends the testcookie back if it's set"
        server addService: '/' action:[:req |
                req send200Response: (req cookieAt: 'testcookie').
        ].

        "Sets the cookie"
        server addService: '/cookie/set' action:[:req |
                req send200Response: 'ok' contentType: 'text/plain'
                        do:[:reply| reply setCookie: 'testcookie' value: '123' path: '/cookie']].

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/cookie/set'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: (resp setCookieAt: 'testcookie') value = '123'.

        resp := client httpGet: self localHostUrl, '/cookie/test'.
        self assert: resp code = 200.
        self assert: resp content = '123'.

        resp := client httpGet: self localHostUrl, '/nocookie/test'.
        self assert: resp code = 200.
        self assert: resp content = ''.
!

----- Method: WebClientServerTest>>testDecodeWebSocketKey (in category 'tests - websockets') -----
testDecodeWebSocketKey
        "Ensure that decoding the Sec-WebSocket-Key fields works properly."

        | key1 key2 data |
        key1 := 155712099.
        key2 := 173347027.
        data := 'Tm[K T2u'.

        self assert: (WebUtils extractWebSocketKey: '18x 6]8vM;54 *(5:  {   U1]8  z [  8') = key1.
        self assert: (WebUtils extractWebSocketKey: '1_ tx7X d  <  nw  334J702) 7]o}` 0') = key2.

        self assert: (WebUtils webSocketHandshake: key1 with: key2 with: data) asString = 'fQJ,fN/4F4!!~K~MH'.!

----- Method: WebClientServerTest>>testDefault404 (in category 'tests - misc') -----
testDefault404
        "Test that a new server responds with 404 to anything"

        | resp |
        resp := WebClient httpGet: self localHostUrl.
        self assert: resp code = 404.
!

----- Method: WebClientServerTest>>testDictionaries (in category 'tests - json') -----
testDictionaries
        "Test dictionary encodings"

        self assert: (self decode: '{}') = (Dictionary new).

        self assert: (self decode: '{"foo" : "bar"}')
                equals: (Dictionary newFromPairs: {
                        'foo'. 'bar'
                }).

        self assert: (self decode: '{"stuff" : [[], 42, "hello"]}')
                equals: (Dictionary newFromPairs: {
                        'stuff'. #(() 42 'hello')
                }).

        self assert: (self decode: '{"x" : 42, "y": "77", "z": 0.1}')
                equals: (Dictionary newFromPairs: {
                        'x'. 42.
                        'y'. '77'.
                        'z'. 0.1
                }).


        self assert: (self encode: Dictionary new) = '{}'.

        self assert: (self encode: (Dictionary newFromPairs: {
                        'foo'. 'bar'
                })) equals: '{"foo": "bar"}'.

        self assert: (self encode: (Dictionary newFromPairs: {
                        'stuff'. #(() 42 'hello')
                })) equals: '{"stuff": [[], 42, "hello"]}'.

        self assert: (self encode: (Dictionary newFromPairs: {
                        'x'. 42.
                        'y'. '77'.
                        'z'. 0.1
                })) equals: '{"x": 42, "y": "77", "z": 0.1}'.

        self should: [self decode: '{'] raise: Error.
        self should: [self decode: '{]'] raise: Error.
        self should: [self decode: '[{]'] raise: Error.
        self should: [self decode: '{"a"}'] raise: Error.
        self should: [self decode: '{42: "hello"}'] raise: Error.
        self should: [self decode: '{"a" : 42,}'] raise: Error.
        self should: [self decode: '{"a" : 42 "b": 33}'] raise: Error.

        self should: [self encode: (Dictionary newFromPairs: {1. 1})] raise: Error.!

----- Method: WebClientServerTest>>testDigestAuth (in category 'tests - auth') -----
testDigestAuth
        "Test client and server handling of digest auth"

        | resp reqHeader |
        server addService: '/test/auth' action:[:req |
                server authenticate: req realm: 'test' methods: #(digest) do:[
                        reqHeader := req headerAt: 'Authorization'.
                        req send200Response: 'ok'
                ].
        ].

        resp := WebClient new httpGet: self localHostUrl, '/test/auth'.
        self assert: resp code = 401.

        [resp := WebClient httpGet: self localHostUrl, '/test/auth']
                on: WebAuthRequired do:[:ex| ex username: 'squeak' password: 'squeak'].
        self assert: resp code = 401.

        [resp := WebClient httpGet: self localHostUrl, '/test/auth']
                on: WebAuthRequired do:[:ex| ex username: 'user' password: 'pass'].
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: (reqHeader beginsWith: 'Digest ').
!

----- Method: WebClientServerTest>>testDuplicateCookies (in category 'tests - cookies') -----
testDuplicateCookies
        "Tests deleting cookies in WebClient"

        | resp client |
        "Sends the testcookie back if it's set"
        server addService: '/' action:[:req |
                req send200Response: (req headersAt: 'cookie') size asString.
        ].

        "Sets the cookie"
        server addService: '/cookie/set' action:[:req |
                req send200Response: 'ok' contentType: 'text/plain'
                        do:[:reply| reply setCookie: 'testcookie' value: '123' path: '/cookie']].

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/cookie/set'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: (resp setCookieAt: 'testcookie') value = '123'.

        resp := client httpGet: self localHostUrl, '/cookie/set'.
        resp := client httpGet: self localHostUrl, '/cookie/set'.

        resp := client httpGet: self localHostUrl, '/cookie/test'.
        self assert: resp code = 200.
        self assert: resp content = '1'.
!

----- Method: WebClientServerTest>>testGetFields (in category 'tests - fields') -----
testGetFields
        "Test client and server handling simple fields"

        | resp |
        server addService: '/fields' action:[:req |
                req send200Response: (String streamContents:[:s|
                        req fields keys asArray sort do:[:key | s cr; nextPutAll: key,'=', (req fields at: key)].
                ]).
        ].

        resp := WebClient httpGet: self localHostUrl, '/fields?foo=123&bar=yoho'.
        self assert: resp code = 200.
        self assert: resp content = '
bar=yoho
foo=123'.
!

----- Method: WebClientServerTest>>testHmacSha1 (in category 'tests - oauth') -----
testHmacSha1
        "Test the SHA1 HMAC algorithm"

        | key message |
        key := 'MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98&'.
        message := 'POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttp%253A%252F%252Flocalhost%253A3005%252Fthe_dance%252Fprocess_callback%253Fservice_provider_id%253D11%26oauth_consumer_key%3DGDdmIQH6jhtmLUypg82g%26oauth_nonce%3DQP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1272323042%26oauth_version%3D1.0'.

        self assert: (WebUtils hmacSha1: message key: key) base64Encoded = '8wUi7m5HFQy76nowoCThusfgB+Q='!

----- Method: WebClientServerTest>>testHtmlSubmit (in category 'tests - misc') -----
testHtmlSubmit
        "Ensure that we have round-trip conversion for html submit"

        | fieldDict fieldList |
        fieldDict := Dictionary newFromPairs: {'foo'. 123. 'bar'. 'yoho'}.
        fieldList := {'foo' -> 123. 'bar' -> 'yoho'}.
        self testHtmlSubmitUsing: fieldDict.
        self testHtmlSubmitUsing: fieldList.!

----- Method: WebClientServerTest>>testHtmlSubmitUsing: (in category 'tests - misc') -----
testHtmlSubmitUsing: fields
        "Ensure that we have round-trip conversion for html submit"

        | resp |
        server addService: '/fields' action:[:req |
                req send200Response: (String streamContents:[:s|
                        req fields keys asArray sort do:[:key | s cr; nextPutAll: key,'=', (req fields at: key)].
                ]).
        ].
        resp := WebClient htmlSubmit: (self localHostUrl, '/fields') fields: fields.
        self assert: resp code = 200.
        self assert: resp content = '
bar=yoho
foo=123'.

        resp := WebClient htmlSubmit: (self localHostUrl, '/fields') fields: fields method: 'POST'.
        self assert: resp code = 200.
        self assert: resp content = '
bar=yoho
foo=123'.
!

----- Method: WebClientServerTest>>testHttpDelete (in category 'tests - methods') -----
testHttpDelete
        "Test for handling a DELETE request"

        | resp |
        server addService: '/nodelete' action:[:req |
                req send200Response: 'ok'.
        ].
        server addService: '/delete' action:[:req |
                req send200Response: req method.
        ] methods: {'GET'. 'PUT'. 'DELETE'}.

        resp := WebClient httpGet: self localHostUrl, '/nodelete'.
        self assert: resp code = 200.
        resp := WebClient httpDelete: self localHostUrl, '/nodelete'.
        self assert: resp code = 405.

        resp := WebClient httpGet: self localHostUrl, '/delete'.
        self assert: resp code = 200.
        resp := WebClient httpDelete: self localHostUrl, '/delete'.
        self assert: resp code = 200.

        resp := WebClient httpPost: self localHostUrl, '/delete' content:'' type: nil.
        self assert: resp code = 405.
!

----- Method: WebClientServerTest>>testHttpHead (in category 'tests - methods') -----
testHttpHead
        "Test for handling a HEAD request"

        | resp |
        server addService: '/foo' action:[:req |
                req send302Response: '/bar'
        ].
        server addService: '/bar' action:[:req |
                req send200Response: 'ok'
        ].

        resp := WebClient httpHead: self localHostUrl, '/bar'.
        self assert: resp code = 200.
        self assert: resp content = ''.
        self deny: resp contentStream isDataAvailable..

        resp := WebClient httpHead: self localHostUrl, '/foo'.
        self assert: resp code = 200.
        self assert: resp content = ''.
!

----- Method: WebClientServerTest>>testHttpOptions (in category 'tests - methods') -----
testHttpOptions
        "Test for handling the OPTIONS request"

        | resp |
        server addService: '/delete' action:[:req |
                req send200Response: req method.
        ] methods: {'GET'. 'PUT'. 'DELETE'}.

        resp := WebClient httpOptions: self localHostUrl, '/*'.
        self assert: resp code = 200.
        self assert: resp content = ''.
        self assert: (resp headerAt: 'allow') = 'HEAD,TRACE,OPTIONS,GET,POST'.

        resp := WebClient httpOptions: self localHostUrl, '/delete'.
        self assert: resp code = 200.
        self assert: resp content = ''.
        self assert: (resp headerAt: 'allow') = 'HEAD,TRACE,OPTIONS,GET,PUT,DELETE'.!

----- Method: WebClientServerTest>>testHttpTrace (in category 'tests - methods') -----
testHttpTrace
        "Test for handling the TRACE request"

        | resp |
        resp := WebClient httpTrace: self localHostUrl, '/bar'.
        self assert: resp code = 200.
        self assert: resp contentType = 'message/http'.
        self assert: resp content =
                ( 'TRACE /bar HTTP/1.1', String crlf,
                'user-agent: ', WebClient new userAgent, String crlf,
                'host: localhost:', self port printString, String crlf, String crlf).
!

----- Method: WebClientServerTest>>testInvalidCookies (in category 'tests - cookies') -----
testInvalidCookies
        "Test client and server handling of cookies"

        | resp client |
        "Sends the testcookie back if it's set"
        server addService: '/' action:[:req |
                req send200Response: (req cookieAt: 'testcookie').
        ].

        "Sets the cookie"
        server addService: '/setcookie' action:[:req | | domain path expires secure |
                domain := req fields at: 'domain' ifAbsent:[nil].
                path := req fields at: 'path' ifAbsent:[nil].
                expires := req fields at: 'expires' ifAbsent:[nil].
                secure := req fields at: 'secure' ifAbsent:['false'].
                req send200Response: 'ok' contentType: 'text/plain'  do:[:reply|
                        reply setCookie: 'testcookie' value: '123' path: path
                                expires: expires domain: domain secure: secure = 'true'.
                ]
        ].

        server addService: '/clearcookie' action:[:req |
                req send200Response: 'ok' contentType: 'text/plain'  do:[:reply|
                        reply setCookie: 'testcookie' value: '123' path: '/'
                                expires: DateAndTime new domain: nil secure: false.
                ]
        ].

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/setcookie?path=/'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: (resp setCookieAt: 'testcookie') value = '123'.
        resp := client httpGet: self localHostUrl, '/'.
        self assert: resp code = 200.
        self assert: resp content = '123'.

        resp := client httpGet: self localHostUrl, '/clearcookie'.
        self assert: resp code = 200.
        resp := client httpGet: self localHostUrl, '/'.
        self assert: resp code = 200.
        self deny: resp content = '123'.

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/setcookie?domain=.foo.com'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: (resp setCookieAt: 'testcookie') value = '123'.

        resp := client httpGet: self localHostUrl, '/'.
        self assert: resp code = 200.
        self deny: resp content = '123'.

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/setcookie?domain=.com'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: (resp setCookieAt: 'testcookie') value = '123'.
        resp := client httpGet: self localHostUrl, '/'.
        self assert: resp code = 200.
        self deny: resp content = '123'.

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/setcookie?secure=true'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: (resp setCookieAt: 'testcookie') value = '123'.
        resp := client httpGet: self localHostUrl, '/'.
        self assert: resp code = 200.
        self deny: resp content = '123'.
!

----- Method: WebClientServerTest>>testListenOnInterface (in category 'tests - misc') -----
testListenOnInterface
        "Test that listening on a particular interface listens only on that interface"

        | localHostAddr resp localHostName client |
        server destroy. "kill old server"

        localHostAddr := NetNameResolver localHostAddress.
        localHostAddr asByteArray = #(127 0 0 1) asByteArray ifTrue:[^self]. "skip test"

        localHostName := NetNameResolver stringFromAddress: localHostAddr.

        server := WebServer new listenOn: self port interface: localHostAddr.
        server addService: '/' action:[:req| req send200Response: 'ok'].

        client := WebClient new.
        client timeout: 1.
        self should:[resp := client httpGet: self localHostUrl, '/test'] raise: Error.

        resp := client httpGet: 'http://', localHostName, ':', self port asString, '/test'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
!

----- Method: WebClientServerTest>>testLogging200 (in category 'tests - misc') -----
testLogging200
        "Test logging a regular 200 a-ok response"

        | resp client log |
        server addService: '/test' action:[:req | req send200Response: 'ok'].
        server accessLog: String new writeStream.

        client := WebClient new.
        client accessLog: String new writeStream.
        [resp := client httpGet: self localHostUrl, '/test'] ensure:[client close].

        self assert: resp code = 200.

        log := server accessLog contents.
        self assert: ('127.0.0.1 - - [*] "GET /test HTTP/1.1" 200 2', String cr match: log).

        log := client accessLog contents.
        self assert: ('localhost - - [*] "GET /test HTTP/1.1" 200 2', String cr match: log).
!

----- Method: WebClientServerTest>>testLogging404 (in category 'tests - misc') -----
testLogging404
        "Test logging a 404 response"

        | resp client log |
        server accessLog: String new writeStream.
        client := WebClient new.
        client accessLog: String new writeStream.
        [resp := client httpGet: self localHostUrl] ensure:[client close].

        self assert: resp code = 404.

        log := server accessLog contents.
        self assert: ('127.0.0.1 - - [*] "GET / HTTP/1.1" 404 145', String cr match: log).

        log := client accessLog contents.
        self assert: ('localhost - - [*] "GET / HTTP/1.1" 404 145', String cr match: log).
!

----- Method: WebClientServerTest>>testMultipartFields (in category 'tests - fields') -----
testMultipartFields
        "Test client and server handling multipart/form-data fields"

        | resp |
        server addService: '/fields' action:[:req |
                req send200Response: (String streamContents:[:s|
                        req fields keys asArray sort do:[:key | s cr; nextPutAll: key,'=', (req fields at: key)].
                ]).
        ].

        resp := WebClient httpPost: self localHostUrl,'/fields'  multipartFields: {
                'foo' -> 123.
                'bar' -> 'yoho'.
        }.
        self assert: resp code = 200.
        self assert: resp content = '
bar=yoho
foo=123'.
!

----- Method: WebClientServerTest>>testMultipartFiles (in category 'tests - fields') -----
testMultipartFiles
        "Test client and server handling multipart/form-data fields for file uploads"

        | resp request document firstPart |
        server addService: '/fields' action:[:req |
                request := req.
                req send200Response: (String streamContents:[:s|
                        req fields keys asArray sort do:[:key | s cr; nextPutAll: key,'=', (req fields at: key)].
                ]).
        ].

        document := MIMEDocument
                                contentType: 'text/webclient-test'
                                content: 'Hello World, this is a sample file'
                                url: FileDirectory default url,'test.txt'.

        resp := WebClient httpPost: self localHostUrl,'/fields'  multipartFields: {
                'upload' -> document.
        }.
        "First test just verifies that uploaded documents look just like other fields"
        self assert: resp code = 200.
        self assert: resp content = '
upload=Hello World, this is a sample file'.

        "Second test verifies that we have the additional data from a file upload"
        firstPart := true.
        request multipartFieldsDo:[:headers :params :content|
                self assert: firstPart. "should only have one part"
                self assert: (params at: 'name') = 'upload'.
                self assert: (params at: 'filename') =
                                                (FileDirectory default fullNameFor: 'test.txt').
                self assert: (headers at: 'content-type') = 'text/webclient-test'.
                firstPart := false.
        ].
!

----- Method: WebClientServerTest>>testMultipartFiles2 (in category 'tests - fields') -----
testMultipartFiles2
        "Same as testMultpartFiles but this time using HTTPSocket API"

        | resp request document firstPart fields |
        server addService: '/fields' action:[:req |
                request := req.
                req send200Response: (String streamContents:[:s|
                        req fields keys asArray sort do:[:key | s cr; nextPutAll: key,'=', (req fields at: key)].
                ]).
        ].

        document := MIMEDocument
                                contentType: 'text/webclient-test'
                                content: 'Hello World, this is a sample file'
                                url: FileDirectory default url,'test.txt'.

        fields := Dictionary new.
        fields at: 'upload' put: {document}.

        "Make sure we're *actually* using HTTPSocket and not WebClient
        (if it's registered as HTTP handler in HTTPSocket)"
        (HTTPSocket respondsTo: #httpRequestHandler:) ifTrue:[
                | oldHandler |
                oldHandler := HTTPSocket httpRequestHandler.
                [HTTPSocket httpRequestHandler: nil.
                resp := (self localHostUrl,'/fields') asUrl postMultipartFormArgs: fields.
                ] ensure:[HTTPSocket httpRequestHandler: oldHandler].
        ] ifFalse:[
                resp := (self localHostUrl,'/fields') asUrl postMultipartFormArgs: fields.
        ].

        "First test just verifies that uploaded documents look just like other fields"
        self assert: resp content = '
upload=Hello World, this is a sample file'.

        "Verifies that we have the additional data from a file upload"
        firstPart := true.
        request multipartFieldsDo:[:headers :params :content|
                self assert: firstPart. "should only have one part"
                self assert: (params at: 'name') = 'upload'.
                self assert: (params at: 'filename') =
                                                (FileDirectory default fullNameFor: 'test.txt').
                self assert: (headers at: 'content-type') = 'text/webclient-test'.
                firstPart := false.
        ].
!

----- Method: WebClientServerTest>>testNestedAction (in category 'tests - misc') -----
testNestedAction
        "Test handling of nested actions"

        | resp |
        server addService: '/test' action:[:req | req send200Response: 'ok'].
        server addService: '/test/42' action:[:req | req send200Response: '42'].

        resp := WebClient httpGet: self localHostUrl, '/test'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.

        resp := WebClient httpGet: self localHostUrl, '/test/42'.
        self assert: resp code = 200.
        self assert: resp content = '42'.

        resp := WebClient httpGet: self localHostUrl, '/test/43'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
!

----- Method: WebClientServerTest>>testNilTrueFalse (in category 'tests - json') -----
testNilTrueFalse
        "Test encodings of nil, true, false"

        self assert: (self decode: 'true') = true.
        self assert: (self decode: 'false') = false.
        self assert: (self decode: 'null') = nil.

        self assert: (self encode: true) = 'true'.
        self assert: (self encode: false) = 'false'.
        self assert: (self encode: nil) = 'null'.

        self should: [self decode: 'nul'] raise: Error.
        self should: [self decode: 'nullll'] raise: Error.
        self should: [self decode: 'tru'] raise: Error.
        self should: [self decode: 'falsef'] raise: Error.
        self should: [self decode: 'truefalse'] raise: Error.
!

----- Method: WebClientServerTest>>testNo302Redirect (in category 'tests - redirect') -----
testNo302Redirect
        "Ensure compliance with RFC 2616 rules prohibiting auto redirect
        for 302/307 responses."

        | resp |
        server addService: '/302' action:[:req |
                req send3xxResponse: '/bar' code: 302
        ] methods: #('GET' 'PUT' 'POST' 'DELETE').
        server addService: '/307' action:[:req |
                req send3xxResponse: '/bar' code: 307
        ] methods: #('GET' 'PUT' 'POST' 'DELETE').
        server addService: '/bar' action:[:req |
                req send200Response: 'redirect ok'
        ] methods: #('GET' 'PUT' 'POST' 'DELETE').

        resp := WebClient httpGet: self localHostUrl, '/302'.
        self assert: resp code = 200.
        self assert: resp content = 'redirect ok'.

        resp := WebClient httpGet: self localHostUrl, '/307'.
        self assert: resp code = 200.
        self assert: resp content = 'redirect ok'.

        resp := WebClient httpPost: self localHostUrl, '/302' content: '' type: 'text/plain'.
        self assert: resp code = 302.
        resp := WebClient httpPost: self localHostUrl, '/307' content: '' type: 'text/plain'.
        self assert: resp code = 307.

        resp := WebClient httpPost: self localHostUrl, '/302' content: '' type: 'text/plain'.
        self assert: resp code = 302.
        resp := WebClient httpPost: self localHostUrl, '/307' content: '' type: 'text/plain'.
        self assert: resp code = 307.

        resp := WebClient httpPut: self localHostUrl, '/302' content: '' type: 'text/plain'.
        self assert: resp code = 302.
        resp := WebClient httpPut: self localHostUrl, '/307' content: '' type: 'text/plain'.
        self assert: resp code = 307.

        resp := WebClient httpDelete: self localHostUrl, '/302'.
        self assert: resp code = 302.
        resp := WebClient httpDelete: self localHostUrl, '/307'.
        self assert: resp code = 307.

!

----- Method: WebClientServerTest>>testNumbers (in category 'tests - json') -----
testNumbers
        "Test the encodings of numbers"

        self assert: 42 equals: (self decode: '42').
        self assert: -123 equals: (self decode: '-0123').
        self assert: 42.3 equals: (self decode: '42.3').
        self assert: -42.3e44 equals: (self decode: '-42.3e44').
        self assert: -0.0 equals: (self decode: '-0.0e0').

        self assert: '42' equals: (self encode: 42).
        self assert: '-123' equals: (self encode: -123).
        self assert: '42.3' equals: (self encode: 42.3).
        self assert: '-4.23e45' equals: (self encode: -42.3e44).
        self assert: '-0.0' equals: (self encode: -0.0e0).

        self should: [self decode: '0x123'] raise: Error.
        self should: [self decode: '-.e'] raise: Error.
!

----- Method: WebClientServerTest>>testPersistentAuthRedirectSession (in category 'tests - redirect') -----
testPersistentAuthRedirectSession
        "Run a test for a full auth-redirect-cookie loop.
        Ensure that the connection is persistent for the entire loop."

        | loginOK finalUrl finalFields firstRedirect client resp url |
        server addService: '/login' action:[:req |
                server authenticate: req realm: 'test' methods: #(digest) do:[ | id |
                        loginOK := true.
                        server sessionAt: (id := UUID new hex) put: ''.
                        req send302Response: (req fields at: 'url' ifAbsent:['/']) unescapePercents
                                do:[:reply| reply setCookie: 'session' value: id path: '/']]].
        server addService: '/action' action:[:req |
                (server sessionAt: (req cookieAt: 'session')) ifNil:[
                        firstRedirect := true.
                        req send302Response: '/login?url=', req rawUrl encodeForHTTP.
                ] ifNotNil:[
                        finalUrl := req url.
                        finalFields := req fields.
                        req send200Response: 'ok'.
                ].
        ].
        firstRedirect := loginOK := false.
        server passwordAt: 'squeak' realm: 'test' put: 'foo'.

        client := WebClient new.
        client allowRedirect: false.
        url :=  self localHostUrl, '/action/foo/bar?string=hello&number=42'.
        [[resp := client httpGet: url] on: WebAuthRequired
                do:[:ex| self assert: client == ex client.
                                self assert: client isConnected.
                                ex username: 'squeak' password: 'foo'.].
        resp code = 302] whileTrue:[
                self assert: client isConnected.
                url := resp headerAt: 'Location'.
        ].
        self assert: client isConnected.
        self assert: resp code = 200.
        self assert: firstRedirect.
        self assert: loginOK.
        self assert: finalUrl = '/action/foo/bar'.
        self assert: finalFields size = 2.
        self assert: (finalFields at: 'string') = 'hello'.
        self assert: (finalFields at: 'number') = '42'.
!

----- Method: WebClientServerTest>>testPostFields (in category 'tests - fields') -----
testPostFields
        "Test client and server handling fields in url-encoded encoded post requests"

        | resp |
        server addService: '/fields' action:[:req |
                req send200Response: (String streamContents:[:s|
                        req fields keys asArray sort do:[:key | s cr; nextPutAll: key,'=', (req fields at: key)].
                ]).
        ].

        resp := WebClient httpPost: self localHostUrl, '/fields'
                                content: 'foo=123&bar=yoho'
                                type: 'application/x-www-form-urlencoded'.
        self assert: resp code = 200.
        self assert: resp content = '
bar=yoho
foo=123'.
!

----- Method: WebClientServerTest>>testRedirect (in category 'tests - redirect') -----
testRedirect
        "Test client and server handling of redirects"

        | resp client |
        server addService: '/foo' action:[:req |
                req send302Response: '/bar'
        ].
        server addService: '/bar' action:[:req |
                req send200Response: 'redirect ok'
        ].

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/foo'.
        self assert: resp code = 200.
        self assert: resp content = 'redirect ok'.

        client allowRedirect: false.
        resp := client httpGet: self localHostUrl, '/foo'.
        self assert: resp code = 302.

        client allowRedirect: true.
        resp := client httpGet: self localHostUrl, '/foo'.
        self assert: resp code = 200.
        self assert: resp content = 'redirect ok'.
!

----- Method: WebClientServerTest>>testRedirectLoop (in category 'tests - redirect') -----
testRedirectLoop
        "Test client handling of redirect loops"

        | resp |
        server addService: '/foo' action:[:req |
                req send302Response: '/bar'
        ].
        server addService: '/bar' action:[:req |
                req send302Response: '/foo'
        ].

        resp := WebClient httpGet: self localHostUrl, '/foo'.
        self assert: resp code = 302.
!

----- Method: WebClientServerTest>>testRedirectTrailingSlash (in category 'tests - redirect') -----
testRedirectTrailingSlash
        "Special test to ensure that a redirect from /foo to /foo/ works"

        | resp client |
        server addService: '/foo' action:[:req |
                req send302Response: '/foo/'
        ].
        server addService: '/foo/' action:[:req |
                req send200Response: 'redirect ok'
        ].

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/foo'.
        self assert: resp code = 200.
        self assert: resp content = 'redirect ok'.
!

----- Method: WebClientServerTest>>testResponseUrl (in category 'tests - misc') -----
testResponseUrl
        "Tests that the response url is pointing to the final location"

        | resp client |
        server addService: '/foo' action:[:req |
                req send302Response: '/bar'
        ].
        server addService: '/bar' action:[:req |
                req send200Response: 'redirect ok'
        ].

        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/foo'.
        self assert: resp code = 200.
        self assert: resp url = (self localHostUrl, '/bar').

        client := WebClient new.
        client allowRedirect: false.
        resp := client httpGet: self localHostUrl, '/foo'.
        self assert: resp code = 302.
        self assert: resp url = (self localHostUrl, '/foo').
!

----- Method: WebClientServerTest>>testSchemeHandling (in category 'tests - https') -----
testSchemeHandling
        "Ensure that we handle only http and https schemes"

        self shouldnt:[WebClient httpGet: '<a href="http://localhost:'">http://localhost:', self port asString] raise: Error.
        self should:[WebClient httpGet: 'ftp://localhost:', self port asString] raise: Error.

!

----- Method: WebClientServerTest>>testServerDestroy (in category 'tests - misc') -----
testServerDestroy
        "Tests that connections get nuked when server gets killed"
       
        | client resp |
        server addService: '/test' action:[:req | req send200Response: 'ok'].
        client := WebClient new.
        resp := client httpGet: self localHostUrl, '/test'.

        self assert: resp code = 200.
        self assert: resp content = 'ok'.
        self assert: client isConnected.
        self assert: server connections size = 1.

        server destroy.

        "Depending on OS the signal may not be delivered synchronously
        and raise ConnectionClosed in the (signaling) peek. Give it a bit of
        time to deal with the close."
        (Delay forMilliseconds: 100) wait.

        "Make stream non-blocking otherwise peek can blow up in our face"
        resp contentStream shouldSignal: false.
        self assert: resp contentStream peek == nil.
        self deny: client isConnected
!

----- Method: WebClientServerTest>>testServerError (in category 'tests - misc') -----
testServerError
        "Test server handling of errors"

        | resp |
        server addService: '/test' action:[:req | self error: 'boom'].

        resp := WebClient httpGet: self localHostUrl, '/test'.
        self assert: resp code = 500.
!

----- Method: WebClientServerTest>>testServerRegistry (in category 'tests - misc') -----
testServerRegistry
        "Ensure that the WebServer registry functions as intended"

        | serverA serverB |
        ["Create a new server"
        serverA := WebServer forUrl: 'http://foo.bar.com'.
        serverA listenOn: self port+1.
        self assert: serverA notNil.
        self assert: serverA isRunning.

        "Look it up again"
        serverB := WebServer forUrl: 'http://foo.bar.com'.
        self assert: serverA == serverB.

        "Override with a new one (should shutdown serverA)"
        serverB := WebServer newForUrl: 'http://foo.bar.com'.
        self deny: serverA == serverB.
        self deny: serverA isRunning.

        "Destroy (should de-register serverB)"
        serverB destroy.
        self assert: (WebServer forUrl: 'http://foo.bar.com' ifAbsent:[nil]) == nil
        ] ensure:[
                serverA ifNotNil:[serverA destroy].
                serverB ifNotNil:[serverB destroy].
        ].!

----- Method: WebClientServerTest>>testSignOAuthGet (in category 'tests - oauth') -----
testSignOAuthGet
        "Ensure that we can sign a request correctly using OAuth.
        Example values taken from
                http://oauth.googlecode.com/svn/code/javascript/example/signature.html
        "

        | request |
        request := WebRequest new.
        request method: 'GET'.
        WebUtils oAuthSign: request
                                url: 'http://host.net/resource'
                                using: self oAuthParams.
        self assert: (request headerAt: 'Authorization')
                equals: 'OAuth oauth_consumer_key="abcd",oauth_nonce="FWNkVaRJVzE",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1281668113",oauth_token="ijkl",oauth_version="1.0",oauth_signature="dSI3zjYnriSnaB787UH2NDcS8Ss%3D"'.
!

----- Method: WebClientServerTest>>testSignOAuthGetDupFields (in category 'tests - oauth') -----
testSignOAuthGetDupFields
        "Ensure that we can sign a request with duplicate fields correctly using OAuth.
        Example values taken from
                http://oauth.googlecode.com/svn/code/javascript/example/signature.html
        "

        | request |
        request := WebRequest new.
        request method: 'GET'.
        WebUtils oAuthSign: request
                                url: 'http://host.net/resource?name=value&name=value'
                                using: self oAuthParams.
        self assert: (request headerAt: 'Authorization')
                equals: 'OAuth oauth_consumer_key="abcd",oauth_nonce="FWNkVaRJVzE",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1281668113",oauth_token="ijkl",oauth_version="1.0",oauth_signature="30p67coAX8YbxCKAGfaimydYK6g%3D"'.
!

----- Method: WebClientServerTest>>testSignOAuthGetFields (in category 'tests - oauth') -----
testSignOAuthGetFields
        "Ensure that we can sign a GET request with fields correctly using OAuth.
        Example values taken from
                http://oauth.googlecode.com/svn/code/javascript/example/signature.html
        "

        | request |
        request := WebRequest new.
        request method: 'GET'.
        WebUtils oAuthSign: request
                                url: 'http://host.net/resource?name=value'
                                using: self oAuthParams.
        self assert: (request headerAt: 'Authorization')
                equals: 'OAuth oauth_consumer_key="abcd",oauth_nonce="FWNkVaRJVzE",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1281668113",oauth_token="ijkl",oauth_version="1.0",oauth_signature="N6TOtNK6h3u9zqjqaF2kgpIVb%2F8%3D"'.
!

----- Method: WebClientServerTest>>testSignOAuthPostFields (in category 'tests - oauth') -----
testSignOAuthPostFields
        "Ensure that we can sign a POST request with fields correctly using OAuth.
        Example values taken from
                http://oauth.googlecode.com/svn/code/javascript/example/signature.html
        "

        | request |
        request := WebRequest new.
        request method: 'POST'.
        WebUtils oAuthSign: request
                                url: 'http://host.net/resource'
                                extra:{'name' -> 'value'}
                                using: self oAuthParams.
        self assert: (request headerAt: 'Authorization')
                equals: 'OAuth oauth_consumer_key="abcd",oauth_nonce="FWNkVaRJVzE",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1281668113",oauth_token="ijkl",oauth_version="1.0",oauth_signature="E7yVjmf%2F8UTF9ij15CtbBBhulMw%3D"'.
!

----- Method: WebClientServerTest>>testSignOAuthUrlEncoding (in category 'tests - oauth') -----
testSignOAuthUrlEncoding
        "Ensure that we can sign a request requiring url-encoded oauth params.
        Example values taken from
                http://oauth.googlecode.com/svn/code/javascript/example/signature.html
        "

        | params request |
        "Consumer Key with url-encoded characters"
        params := self oAuthParams.
        params at: 'oauth_consumer_key' put: 'key with spaces'.

        request := WebRequest new.
        request method: 'GET'.
        WebUtils oAuthSign: request
                                url: 'http://host.net/resource'
                                using: params.
        self assert: (request headerAt: 'Authorization')
                equals: 'OAuth oauth_consumer_key="key%20with%20spaces",oauth_nonce="FWNkVaRJVzE",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1281668113",oauth_token="ijkl",oauth_version="1.0",oauth_signature="%2FxrAsx0Utt3V6ZbX00jWWpkqrvg%3D"'.!

----- Method: WebClientServerTest>>testSimpleServerAction (in category 'tests - misc') -----
testSimpleServerAction
        "Test client and server handling simple defaults"

        | resp srvr |
        server addService: '/test' action:[:req |
                srvr := req server.
                req send200Response: 'ok'].

        resp := WebClient httpGet: self localHostUrl, '/test'.
        self assert: srvr notNil.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
!

----- Method: WebClientServerTest>>testStreaming (in category 'tests - misc') -----
testStreaming
        "Run a test for a full auth-redirect-cookie loop"

        | resp amount stream |
        amount := 128*1024.
        server addService: '/streaming' action:[:req|
                req stream200Response: (String new: amount) readStream size: amount.
        ].
        resp := WebClient httpGet: self localHostUrl, '/streaming'.
        self assert: resp isSuccess.
        stream := WriteStream on: String new.
        resp streamTo: stream size: resp contentLength progress: nil.
        self assert: stream position = amount.
!

----- Method: WebClientServerTest>>testStrings (in category 'tests - json') -----
testStrings
        "Test string encodings"

        self assert: (self decode: '"Hello World"') = 'Hello World'.
        self assert: (self decode: '"\"Hello World\""') = '"Hello World"'.
        self assert: (self decode: '"foo\\bar\/baz"') = 'foo\bar/baz'.
        self assert: (self decode: '""') = ''.
        self assert: (self decode: '"foo \u0026 bar"') = 'foo & bar'.
        self assert: (self decode: '"\r\n"') = String crlf.
        self assert: (self decode: '"\u041F\u0440\u0430\u0432\u0434\u0430"')
                equals: #[208 159 209 128 208 176 208 178 208 180 208 176] asString utf8ToSqueak.

        self assert: (self encode: 'Hello World') = '"Hello World"'.
        self assert: (self encode: '"Hello World"') = '"\"Hello World\""'.
        self assert: (self encode: 'foo\bar/baz') = '"foo\\bar\/baz"'.
        self assert: (self encode: '') = '""'.
        self assert: (self encode: 'foo ', (Character value: 257),' bar') = '"foo \u0101 bar"'.
        self assert: (self encode: String crlf) = '"\r\n"'.

        self assert: (self encode: #[208 159 209 128 208 176 208 178 208 180 208 176] asString utf8ToSqueak)
                equals: '"\u041F\u0440\u0430\u0432\u0434\u0430"'.

        self should: [self decode: '"hello'] raise: Error.
        self should: [self decode: '"\'] raise: Error.
        self should: [self decode: '"\"'] raise: Error.!

----- Method: WebClientServerTest>>testTransientPostContent (in category 'tests - misc') -----
testTransientPostContent
        "Ensure that WebRequest>>content doesn't close the socket
        when used in a transient post request"

        | resp client |
        server addService: '/test' action:[:req |
                req send200Response: req content].

        client := WebClient new.
        resp := client httpPost: self localHostUrl, '/test' content:'hello' type: nil do:[:req|
                req protocol: 'HTTP/1.0'
        ].
        self assert: resp code = 200.
        self assert: resp content = 'hello'.
!

----- Method: WebClientServerTest>>testUrlEncoding (in category 'tests - misc') -----
testUrlEncoding
        "Test the default URL encoding behavior"
        | resp |
        server addService: '/hello world' action:[:req|
                req send200Response: 'ok'.
        ].
        resp := WebClient httpGet: self localHostUrl, '/hello%20world'.
        self assert: resp code = 200.
        self assert: resp content = 'ok'.

        resp := WebClient httpGet: (WebUtils urlEncode: self localHostUrl, '/hello world').
        self assert: resp code = 200.
        self assert: resp content = 'ok'.
!

----- Method: WebClientServerTest>>testWebSocketHash07 (in category 'tests - websockets') -----
testWebSocketHash07
        "self run: #testWebSocketHash07"
        "From http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07"

        | hash key |
        key := 'dGhlIHNhbXBsZSBub25jZQ=='.
        hash := WebUtils webSocketHash07: key.
        self assert: hash = 's3pPLMBiTxaQ9kYGzzhZRbK+xOo='.!

----- Method: WebClientServerTest>>testWebSockets (in category 'tests - websockets') -----
testWebSockets
        "Test the WebSocket interface"

        self testWebSockets:[
                WebClient webSocketTo: self localHostUrl,'/websockets'.
        ].
!

----- Method: WebClientServerTest>>testWebSockets00 (in category 'tests - websockets') -----
testWebSockets00
        "Test the WebSocket interface"

        self testWebSockets:[
                WebClient webSocket00: self localHostUrl,'/websockets' protocol: 'sample'
        ].
!

----- Method: WebClientServerTest>>testWebSockets07 (in category 'tests - websockets') -----
testWebSockets07
        "Test the WebSocket interface"

        self testWebSockets:[
                WebClient webSocket07: self localHostUrl,'/websockets' protocol: 'sample'
        ].
!

----- Method: WebClientServerTest>>testWebSockets07ControlDecode (in category 'tests - websockets') -----
testWebSockets07ControlDecode
        "Tests from section 4.7 of the WebSockets spec"

        | ws ping pong |
        ws := WebSocket07 new.
        ws onPing:[:msg| ping := msg asString].
        ws onPong:[:msg| pong := msg asString].

        ping := pong := nil.
        ws readFrameFrom:
                #[16r89 16r05 16r48 16r65 16r6C 16r6C 16r6F] readStream.
                "contains a body of 'Hello', but the contents of the body are arbitrary)"
        self assert: ping = 'Hello'.
        self assert: pong = nil.

        ping := pong := nil.
        ws readFrameFrom:
                #[16r8A 16r05 16r48 16r65 16r6C 16r6C 16r6F] readStream.
                "contains a body of 'Hello', matching the body of the ping)"
        self assert: ping = nil.
        self assert: pong = 'Hello'.
!

----- Method: WebClientServerTest>>testWebSockets07ControlInterleave (in category 'tests - websockets') -----
testWebSockets07ControlInterleave
        "Test the WebSocket 07 control interleave"

        | sema cws resp ping pong |
        ping := 'Hello Ping'.
        server addService: '/websockets' action:[:req | | sws |
                sws := req asWebSocket.
                sws onMessage:[:sdata| sws send: sdata].
                sws onPing:[:sdata| sws pong: sdata].
                sws onError:[:ex|
                        Transcript cr; show: ex description.
                        Transcript cr; show: ex signalerContext longStack.
                ].
                sws run.
        ].

        sema := Semaphore new.
        cws := WebClient webSocket07: self localHostUrl,'/websockets' protocol: nil.
        cws onMessage:[:cdata| resp := cdata. sema signal].
        cws onPong:[:cdata| pong := cdata asString. sema signal.].
        cws fork.

        cws send: 'Über-cool'.
        sema wait.
        self assert: (resp = 'Über-cool').

        cws firstFragment: 'First,'.
        cws nextFragment: 'Next,'.
        cws lastFragment: 'Last.'.
        sema wait.
        self assert: (resp = 'First,Next,Last.').
        self deny: sema isSignaled.

        resp := nil.
        cws firstFragment: 'First,'.
        cws ping: ping.
        sema wait.

        self assert: ping = pong. "i.e., roundtrip finished"
        self assert: resp == nil. "i.e., fragment not delivered yet"
        self deny: sema isSignaled.

        cws nextFragment: 'Next,'.
        cws lastFragment: 'Last.'.
        sema wait.
        self assert: (resp = 'First,Next,Last.').


        cws close.!

----- Method: WebClientServerTest>>testWebSockets07DataDecode (in category 'tests - websockets') -----
testWebSockets07DataDecode
        "Tests from section 4.7 of the WebSockets spec"

        | data ws |
        ws := WebSocket07 new.
        ws onMessage:[:msg| data := msg].
        "A single-frame unmasked text message"
        data := nil.
        ws readFrameFrom:
                #[16r81 16r05 16r48 16r65 16r6C 16r6C 16r6F] readStream.
        self assert: data = 'Hello'.

        "A single-frame masked text message"
        data := nil.
        ws readFrameFrom:
                #[16r81 16r85 16r37 16rFA 16r21 16r3D 16r7F 16r9F 16r4D 16r51 16r58] readStream.
        self assert: data = 'Hello'.

        "A fragmented unmasked text message"
        data := nil.
        ws readFrameFrom:
                #[16r01 16r03 16r48 16r65 16r6C] readStream. "contains 'Hel'"
        self assert: data = nil.
        ws readFrameFrom:
                #[16r80 16r02 16r6C 16r6F] readStream. "contains 'lo'"
        self assert: data = 'Hello'.
!

----- Method: WebClientServerTest>>testWebSockets07NoMask (in category 'tests - websockets') -----
testWebSockets07NoMask
        "Test the WebSocket interface"

        self testWebSockets:[
                (WebClient webSocket07: self localHostUrl,'/websockets' protocol: 'sample')
                        masking: false;
                        yourself].
!

----- Method: WebClientServerTest>>testWebSockets68 (in category 'tests - websockets') -----
testWebSockets68
        "Test the WebSocket interface"

        self testWebSockets:[
                WebClient webSocket68: self localHostUrl,'/websockets' protocol: 'sample'.
        ].
!

----- Method: WebClientServerTest>>testWebSockets: (in category 'tests - websockets') -----
testWebSockets: aBlock
        "Test the WebSocket interface"

        | sema cws resp sws |
        server addService: '/websockets' action:[:req |
                sws := req asWebSocket.
                sws onError:[:ex|
                        Transcript cr; show: ex description.
                        Transcript cr; show: ex signalerContext longStack.
                ].
                sws onMessage:[:sdata| sws send: 'Response: ', sdata].
                sws run.
        ].

        sema := Semaphore new.
        cws := aBlock value.
        cws onMessage:[:cdata| resp := cdata. sema signal].
        cws onClose:[resp := nil. sema signal].
        cws fork.

        cws send: 'Testing, one, two'.
        sema wait.
        self assert: (resp = 'Response: Testing, one, two').

        cws close.
        sema wait.
        self assert: (resp = nil).
!

----- Method: WebClientServerTest>>testWebSocketsFraming (in category 'tests - websockets') -----
testWebSocketsFraming
        "Test the WebSocket 00 framing"

        | sema cws resp frameType |
        server addService: '/websockets' action:[:req | | sws |
                sws := req asWebSocket.
                sws onMessage:[:sdata :type| sws send: sdata type: type].
                sws onError:[:ex|
                        Transcript cr; show: ex description.
                        Transcript cr; show: ex signalerContext longStack.
                ].
                sws run.
        ].

        sema := Semaphore new.
        cws := WebClient webSocket00: self localHostUrl,'/websockets' protocol: nil.
        cws onMessage:[:cdata :type| resp := cdata. frameType := type. sema signal].
        cws onClose:[resp := nil. frameType := 255. sema signal].
        cws fork.

        cws send: 'Über-cool'.
        sema wait.
        self assert: (resp = 'Über-cool').
        self assert: (frameType = 0).

        cws send: 'Über-funny' type: 15.
        sema wait.
        self assert: (resp = 'Über-funny').
        self assert: (frameType = 15).

        cws send: (String new: 100) type: 0.
        sema wait.
        self assert: (resp = (String new: 100)).
        self assert: (frameType = 0).

        cws send: (ByteArray new: 100) type: 130.
        sema wait.
        self assert: (resp = (ByteArray new: 100)).
        self assert: (frameType = 130).

        cws close.
        sema wait.
        self assert: (resp = nil).
        self assert: (frameType = 255).!