From f2f95b24817de9a066da244c2f2d48e2596ccf2d Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Tue, 22 Sep 2026 23:18:21 +0800 Subject: [PATCH 1/3] fix(cloud-sql): postgres servlet reported a failed vote as success The catch block writes a 500 and the error text, but without a return the code falls through to setStatus(200) and the "vote successfully cast" message, so a failed insert looked like a success to the user. Added the missing return. The line sits inside the START_EXCLUDE region, so the published snippet is unchanged. --- .../src/main/java/com/example/cloudsql/IndexServlet.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloud-sql/postgres/servlet/src/main/java/com/example/cloudsql/IndexServlet.java b/cloud-sql/postgres/servlet/src/main/java/com/example/cloudsql/IndexServlet.java index 10e73fd094c..50218c92236 100644 --- a/cloud-sql/postgres/servlet/src/main/java/com/example/cloudsql/IndexServlet.java +++ b/cloud-sql/postgres/servlet/src/main/java/com/example/cloudsql/IndexServlet.java @@ -123,6 +123,8 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOEx .write( "Unable to successfully cast vote! Please check the application " + "logs for more details."); + // Without this return the success response below overwrites the 500 and the error text. + return; // [END_EXCLUDE] } // [END cloud_sql_postgres_servlet_connection] From 02fce1e03467fd7c0d406cbd8c021f999eaa295e Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Tue, 22 Sep 2026 23:18:26 +0800 Subject: [PATCH 2/3] fix(cloud-sql): same missing return in the mysql and sqlserver servlets Both have the identical fall-through after the error response. --- .../src/main/java/com/example/cloudsql/IndexServlet.java | 2 ++ .../src/main/java/com/example/cloudsql/IndexServlet.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/IndexServlet.java b/cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/IndexServlet.java index 6551d57e899..06906010ad2 100644 --- a/cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/IndexServlet.java +++ b/cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/IndexServlet.java @@ -105,6 +105,8 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOEx .write( "Unable to successfully cast vote! Please check the application " + "logs for more details."); + // Without this return the success response below overwrites the 500 and the error text. + return; // [END_EXCLUDE] } // [END cloud_sql_mysql_servlet_connection] diff --git a/cloud-sql/sqlserver/servlet/src/main/java/com/example/cloudsql/IndexServlet.java b/cloud-sql/sqlserver/servlet/src/main/java/com/example/cloudsql/IndexServlet.java index 60aa1ba8621..890ab5254ac 100644 --- a/cloud-sql/sqlserver/servlet/src/main/java/com/example/cloudsql/IndexServlet.java +++ b/cloud-sql/sqlserver/servlet/src/main/java/com/example/cloudsql/IndexServlet.java @@ -97,6 +97,8 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) resp.setStatus(500); resp.getWriter().write("Unable to successfully cast vote! Please check the application " + "logs for more details."); + // Without this return the success response below overwrites the 500 and the error text. + return; // [END_EXCLUDE] } // [END cloud_sql_sqlserver_servlet_connection] From 445978ce4cffd8cd99873333b44eb03a24be8967 Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Tue, 22 Sep 2026 23:18:26 +0800 Subject: [PATCH 3/3] test(cloud-sql): cover the failed-vote response path in the three servlets Mocks a DataSource that throws SQLException so doPost takes the catch branch, and asserts the status is 500 and the body does not claim the vote was cast. Reverting the return makes these fail with 200. --- .../TestIndexServletMysqlErrorResponse.java | 70 +++++++++++++++++++ ...TestIndexServletPostgresErrorResponse.java | 70 +++++++++++++++++++ ...estIndexServletSqlServerErrorResponse.java | 70 +++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 cloud-sql/mysql/servlet/src/test/java/com/example/cloudsql/TestIndexServletMysqlErrorResponse.java create mode 100644 cloud-sql/postgres/servlet/src/test/java/com/example/cloudsql/TestIndexServletPostgresErrorResponse.java create mode 100644 cloud-sql/sqlserver/servlet/src/test/java/com/example/cloudsql/TestIndexServletSqlServerErrorResponse.java diff --git a/cloud-sql/mysql/servlet/src/test/java/com/example/cloudsql/TestIndexServletMysqlErrorResponse.java b/cloud-sql/mysql/servlet/src/test/java/com/example/cloudsql/TestIndexServletMysqlErrorResponse.java new file mode 100644 index 00000000000..c19604e9c61 --- /dev/null +++ b/cloud-sql/mysql/servlet/src/test/java/com/example/cloudsql/TestIndexServletMysqlErrorResponse.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cloudsql; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicInteger; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.sql.DataSource; +import org.junit.Test; + +/** Covers the doPost error path, which does not need a database. */ +public class TestIndexServletMysqlErrorResponse { + + @Test + public void testFailedVoteIsNotReportedAsSuccess() throws Exception { + DataSource pool = mock(DataSource.class); + when(pool.getConnection()).thenThrow(new SQLException("connection refused")); + + ServletContext context = mock(ServletContext.class); + when(context.getAttribute("my-pool")).thenReturn(pool); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getServletContext()).thenReturn(context); + when(request.getParameter("team")).thenReturn("TABS"); + + StringWriter body = new StringWriter(); + PrintWriter writer = new PrintWriter(body); + AtomicInteger status = new AtomicInteger(); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(writer); + doAnswer( + invocation -> { + status.set(invocation.getArgument(0)); + return null; + }) + .when(response) + .setStatus(anyInt()); + + new IndexServlet().doPost(request, response); + writer.flush(); + + assertThat(status.get()).isEqualTo(500); + assertThat(body.toString()).contains("Unable to successfully cast vote"); + assertThat(body.toString()).doesNotContain("Vote successfully cast"); + } +} diff --git a/cloud-sql/postgres/servlet/src/test/java/com/example/cloudsql/TestIndexServletPostgresErrorResponse.java b/cloud-sql/postgres/servlet/src/test/java/com/example/cloudsql/TestIndexServletPostgresErrorResponse.java new file mode 100644 index 00000000000..a255abc20e3 --- /dev/null +++ b/cloud-sql/postgres/servlet/src/test/java/com/example/cloudsql/TestIndexServletPostgresErrorResponse.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cloudsql; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicInteger; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.sql.DataSource; +import org.junit.Test; + +/** Covers the doPost error path, which does not need a database. */ +public class TestIndexServletPostgresErrorResponse { + + @Test + public void testFailedVoteIsNotReportedAsSuccess() throws Exception { + DataSource pool = mock(DataSource.class); + when(pool.getConnection()).thenThrow(new SQLException("connection refused")); + + ServletContext context = mock(ServletContext.class); + when(context.getAttribute("my-pool")).thenReturn(pool); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getServletContext()).thenReturn(context); + when(request.getParameter("team")).thenReturn("TABS"); + + StringWriter body = new StringWriter(); + PrintWriter writer = new PrintWriter(body); + AtomicInteger status = new AtomicInteger(); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(writer); + doAnswer( + invocation -> { + status.set(invocation.getArgument(0)); + return null; + }) + .when(response) + .setStatus(anyInt()); + + new IndexServlet().doPost(request, response); + writer.flush(); + + assertThat(status.get()).isEqualTo(500); + assertThat(body.toString()).contains("Unable to successfully cast vote"); + assertThat(body.toString()).doesNotContain("Vote successfully cast"); + } +} diff --git a/cloud-sql/sqlserver/servlet/src/test/java/com/example/cloudsql/TestIndexServletSqlServerErrorResponse.java b/cloud-sql/sqlserver/servlet/src/test/java/com/example/cloudsql/TestIndexServletSqlServerErrorResponse.java new file mode 100644 index 00000000000..dddff5908b1 --- /dev/null +++ b/cloud-sql/sqlserver/servlet/src/test/java/com/example/cloudsql/TestIndexServletSqlServerErrorResponse.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cloudsql; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicInteger; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.sql.DataSource; +import org.junit.Test; + +/** Covers the doPost error path, which does not need a database. */ +public class TestIndexServletSqlServerErrorResponse { + + @Test + public void testFailedVoteIsNotReportedAsSuccess() throws Exception { + DataSource pool = mock(DataSource.class); + when(pool.getConnection()).thenThrow(new SQLException("connection refused")); + + ServletContext context = mock(ServletContext.class); + when(context.getAttribute("my-pool")).thenReturn(pool); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getServletContext()).thenReturn(context); + when(request.getParameter("team")).thenReturn("TABS"); + + StringWriter body = new StringWriter(); + PrintWriter writer = new PrintWriter(body); + AtomicInteger status = new AtomicInteger(); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(writer); + doAnswer( + invocation -> { + status.set(invocation.getArgument(0)); + return null; + }) + .when(response) + .setStatus(anyInt()); + + new IndexServlet().doPost(request, response); + writer.flush(); + + assertThat(status.get()).isEqualTo(500); + assertThat(body.toString()).contains("Unable to successfully cast vote"); + assertThat(body.toString()).doesNotContain("Vote successfully cast"); + } +}