diff --git a/.gitignore b/.gitignore index bee8a64b..e504efbc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ __pycache__ +.pytest_cache +node_modules +.venv +# rust build artifacts +target +# typescript build output from npm test +build +# make test output +test-results.log diff --git a/go/hello/function.go b/go/hello/function.go index 62380444..4ff71888 100644 --- a/go/hello/function.go +++ b/go/hello/function.go @@ -1,6 +1,7 @@ package function import ( + "encoding/json" "fmt" "net/http" ) @@ -17,6 +18,7 @@ func New() *MyFunction { // Handle an HTTP Request. func (f *MyFunction) Handle(w http.ResponseWriter, r *http.Request) { - fmt.Print("Received a request\n") //printed on server - fmt.Fprint(w, "Hello Go World!") //send to client + fmt.Print("Received a request\n") //printed on server + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"message": "Hello Go World!"}) } diff --git a/go/hello/function_test.go b/go/hello/function_test.go index d9abb449..a6570c39 100644 --- a/go/hello/function_test.go +++ b/go/hello/function_test.go @@ -1,6 +1,7 @@ package function import ( + "encoding/json" "net/http" "net/http/httptest" "testing" @@ -22,4 +23,16 @@ func TestHandle(t *testing.T) { if res.StatusCode != 200 { t.Fatalf("unexpected response code: %v", res.StatusCode) } + + if ct := w.Header().Get("Content-Type"); ct != "application/json" { + t.Fatalf("unexpected content type: %v", ct) + } + + var resp map[string]string + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if resp["message"] != "Hello Go World!" { + t.Fatalf("unexpected message: %v", resp["message"]) + } } diff --git a/node/hello/index.js b/node/hello/index.js index 99088fcb..f4749c2c 100644 --- a/node/hello/index.js +++ b/node/hello/index.js @@ -19,9 +19,9 @@ const handle = async (context, body) => { console.log("Recieved request!") // printed on server // If the request is an HTTP GET/POST if (context.method === 'POST' || context.method === 'GET'){ - return { statusMessage: "Hello Node World!"} // send to client + return { body: { message: "Hello Node World!" } } // send to client } else { - return { statusCode: 405, statusMessage: 'Method not allowed' }; + return { statusCode: 405, body: 'Method not allowed' }; } } diff --git a/node/hello/test/integration.js b/node/hello/test/integration.js index 6776f004..1d85b645 100644 --- a/node/hello/test/integration.js +++ b/node/hello/test/integration.js @@ -15,12 +15,12 @@ test('Integration: handles an HTTP GET', t => { start(func).then(server => { t.plan(2); request(server) - .get('/?name=tiger') + .get('/') .expect(200) .expect('Content-Type', /json/) .end((err, res) => { t.error(err, 'No error'); - t.deepEqual(res.body, { query: { name: 'tiger' } }); + t.deepEqual(res.body, { message: 'Hello Node World!' }); t.end(); server.close(); }); @@ -32,12 +32,11 @@ test('Integration: handles an HTTP POST', t => { t.plan(2); request(server) .post('/') - .send({ name: 'tiger' }) .expect(200) .expect('Content-Type', /json/) .end((err, res) => { t.error(err, 'No error'); - t.deepEqual(res.body, { name: 'tiger' }); + t.deepEqual(res.body, { message: 'Hello Node World!' }); t.end(); server.close(); }); @@ -49,7 +48,6 @@ test('Integration: responds with error code if neither GET or POST', t => { t.plan(1); request(server) .put('/') - .send({ name: 'tiger' }) .expect(200) .expect('Content-Type', /json/) .end((err, res) => { diff --git a/node/hello/test/unit.js b/node/hello/test/unit.js index a0834efb..cc8c671e 100644 --- a/node/hello/test/unit.js +++ b/node/hello/test/unit.js @@ -7,24 +7,21 @@ const fixture = { log: { info: console.log } }; test('Unit: handles an HTTP GET', async t => { t.plan(1); - // Invoke the function, which should complete without error. - const result = await func({ ...fixture, method: 'GET', query: { name: 'tiger' } }); - t.deepEqual(result, { query: { name: 'tiger' } }); + const result = await func({ ...fixture, method: 'GET' }); + t.deepEqual(result, { body: { message: 'Hello Node World!' } }); t.end(); }); test('Unit: handles an HTTP POST', async t => { t.plan(1); - const body = { name: 'tiger' }; - // Invoke the function, which should complete without error. - const result = await func({ ...fixture, method: 'POST', body }, body); - t.deepEqual(result, { body }); + const result = await func({ ...fixture, method: 'POST' }); + t.deepEqual(result, { body: { message: 'Hello Node World!' } }); t.end(); }); test('Unit: responds with error code if neither GET or POST', async t => { t.plan(1); const result = await func(fixture); - t.deepEqual(result, { statusCode: 405, statusMessage: 'Method not allowed' }); + t.deepEqual(result, { statusCode: 405, body: 'Method not allowed' }); t.end(); }); diff --git a/python/echo/tests/test_func.py b/python/echo/tests/test_func.py index 5b37a735..c7a5889c 100644 --- a/python/echo/tests/test_func.py +++ b/python/echo/tests/test_func.py @@ -1,38 +1,64 @@ """ -An example set of unit tests which confirm that the main handler (the -callable function) returns 200 OK for a simple HTTP GET. +Unit tests for the echo function. Verifies that GET echoes the query string +and POST echoes the request body. """ import pytest from function import new +@pytest.mark.asyncio +async def test_get_echoes_query_string(): + f = new() + + sent_body = None + + async def send(message): + nonlocal sent_body + # capture the body (second send() call) + if message.get('type') == 'http.response.body': + sent_body = message.get('body') + + scope = {'method': 'GET', 'query_string': b'message=hello'} + await f.handle(scope, None, send) + + assert sent_body == b'message=hello', f"Unexpected body: {sent_body}" + @pytest.mark.asyncio -async def test_function_handle(): - f = new() # Instantiate Function to Test +async def test_post_echoes_request_body(): + f = new() - sent_ok = False - sent_headers = False - sent_body = False + sent_body = None - # Mock Send async def send(message): - nonlocal sent_ok - nonlocal sent_headers nonlocal sent_body + # capture the body (second send() call) + if message.get('type') == 'http.response.body': + sent_body = message.get('body') + + request_body = b'{"message":"Hello World"}' + + async def receive(): + return {'body': request_body, 'more_body': False} - if message.get('status') == 200: - sent_ok = True + scope = {'method': 'POST'} + await f.handle(scope, receive, send) - if message.get('type') == 'http.response.start': - sent_headers = True + assert sent_body == request_body, f"Unexpected body: {sent_body}" + +@pytest.mark.asyncio +async def test_get_empty_query_string(): + f = new() + + sent_body = None + + async def send(message): + nonlocal sent_body + # capture the body (second send() call) if message.get('type') == 'http.response.body': - sent_body = True + sent_body = message.get('body') - # Invoke the Function - await f.handle({}, {}, send) + scope = {'method': 'GET'} + await f.handle(scope, None, send) - # Assert send was called - assert sent_ok, "Function did not send a 200 OK" - assert sent_headers, "Function did not send headers" - assert sent_body, "Function did not send a body" + assert sent_body == b'', f"Unexpected body: {sent_body}" diff --git a/python/hello/function/func.py b/python/hello/function/func.py index a3e7068c..1806d997 100644 --- a/python/hello/function/func.py +++ b/python/hello/function/func.py @@ -8,7 +8,6 @@ def new(): """ return Function() - class Function: def __init__(self): """ The init method is an optional method where initialization can be @@ -22,17 +21,16 @@ async def handle(self, scope, receive, send): logging.info("OK: Request Received") - # echo the request to the calling client await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ - [b'content-type', b'text/plain'], + [b'content-type', b'application/json'], ], }) await send({ 'type': 'http.response.body', - 'body': 'Hello Python World!'.encode(), + 'body': '{"message":"Hello Python World!"}'.encode(), }) def start(self, cfg): diff --git a/python/hello/tests/test_func.py b/python/hello/tests/test_func.py index 5b37a735..a466f0fb 100644 --- a/python/hello/tests/test_func.py +++ b/python/hello/tests/test_func.py @@ -5,14 +5,13 @@ import pytest from function import new - @pytest.mark.asyncio async def test_function_handle(): f = new() # Instantiate Function to Test sent_ok = False sent_headers = False - sent_body = False + sent_body = None # Mock Send async def send(message): @@ -27,7 +26,7 @@ async def send(message): sent_headers = True if message.get('type') == 'http.response.body': - sent_body = True + sent_body = message.get('body') # Invoke the Function await f.handle({}, {}, send) @@ -35,4 +34,4 @@ async def send(message): # Assert send was called assert sent_ok, "Function did not send a 200 OK" assert sent_headers, "Function did not send headers" - assert sent_body, "Function did not send a body" + assert sent_body == b'{"message":"Hello Python World!"}', f"Unexpected body: {sent_body}" diff --git a/rust/hello/src/handler.rs b/rust/hello/src/handler.rs index ae1017fd..0e9c07a9 100644 --- a/rust/hello/src/handler.rs +++ b/rust/hello/src/handler.rs @@ -6,7 +6,9 @@ use log::info; pub async fn index(req: HttpRequest, config: Data) -> HttpResponse { info!("{:#?}", req); if req.method() == Method::GET || req.method() == Method::POST{ - HttpResponse::Ok().body(format!("{}\n",config.response)) + HttpResponse::Ok() + .content_type("application/json") + .body(format!("{{\"message\":\"{}\"}}", config.response)) } else { HttpResponse::Ok().body("Unknown method\n") } @@ -27,7 +29,7 @@ mod tests { let resp = index(req, config()).await; assert_eq!(resp.status(), http::StatusCode::OK); assert_eq!( - &Bytes::from("Hello Rust World!\n"), + &Bytes::from("{\"message\":\"Hello Rust World!\"}"), to_bytes(resp.into_body()).await.unwrap().as_ref() ); } @@ -38,7 +40,7 @@ mod tests { let resp = index(req, config()).await; assert!(resp.status().is_success()); assert_eq!( - &Bytes::from("Hello Rust World!\n"), + &Bytes::from("{\"message\":\"Hello Rust World!\"}"), to_bytes(resp.into_body()).await.unwrap().as_ref() ); } diff --git a/springboot/hello/src/main/java/functions/CloudFunctionApplication.java b/springboot/hello/src/main/java/functions/CloudFunctionApplication.java index fef5d1bd..233b8b28 100644 --- a/springboot/hello/src/main/java/functions/CloudFunctionApplication.java +++ b/springboot/hello/src/main/java/functions/CloudFunctionApplication.java @@ -16,6 +16,6 @@ public static void main(String[] args) { @Bean public Function, String> echo() { - return inputMessage -> "Hello Springboot World!"; + return inputMessage -> "{\"message\":\"Hello Springboot World!\"}"; } } diff --git a/springboot/hello/src/test/java/functions/EchoCaseFunctionTest.java b/springboot/hello/src/test/java/functions/EchoCaseFunctionTest.java index ecefd6c6..1c0a4992 100644 --- a/springboot/hello/src/test/java/functions/EchoCaseFunctionTest.java +++ b/springboot/hello/src/test/java/functions/EchoCaseFunctionTest.java @@ -33,6 +33,6 @@ public void testEcho() throws Exception { ResponseEntity response = this.rest.exchange(request,String.class); // assert status code and return value assertThat(response.getStatusCode().value(), equalTo(200)); - assertThat(response.getBody(), containsString("Hello Springboot World!")); + assertThat(response.getBody(), containsString("{\"message\":\"Hello Springboot World!\"}")); } }