import java.nio.file.*; import java.sql.*; import java.util.*; import java.util.regex.*; /** * Reproduces GET /api/merchant/onboarding/status against the development database, down to the * statements the service actually issues. Read-only. * *

Also compares the development schema with the additive columns * {@link com.talentcard.server.business.merchant.MerchantOnboardingSchemaInitializer} is supposed to * add at startup: that initializer deliberately swallows "could not take the lock" failures, so a * column can be silently missing without any request having failed yet. * * usage: java -cp :. ProbeOnboardingProgress.java [jdbcUrl] [initializerSource] */ public class ProbeOnboardingProgress { public static void main(String[] args) throws Exception { String url = args.length > 0 ? args[0] : "jdbc:mysql://127.0.0.1:8134/talent_card?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false"; String source = args.length > 1 ? args[1] : "../../talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantOnboardingSchemaInitializer.java"; String database = url.split("\\?")[0].substring(url.split("\\?")[0].lastIndexOf('/') + 1); boolean red = false; try (Connection connection = DriverManager.getConnection(url, "root", "123456")) { connection.setReadOnly(true); for (String table : new String[] { "merchant_onboarding_progress", "merchant_onboarding_attempt" }) { boolean exists = exists(connection, database, table); System.out.println("table " + table + " exists = " + exists); } System.out.println(); // The two statements MerchantOnboardingProgressService issues on the status path. red |= run(connection, "select * from merchant_onboarding_progress where application_id=?", "view()", 1L); red |= run(connection, "select * from merchant_onboarding_attempt where application_id=? and step='delay' order by id desc limit 1", "delayAttempt()", 1L); // The statements MerchantSettlementService issues before it reaches the progress view. red |= run(connection, "select * from merchant_onboarding_application where merchant_id=? and store_id=? and initial_bank_card_id is not null order by id desc limit 1", "onboardingStatus()", -1L, -1L); red |= run(connection, "select id from merchant_store where id=? and merchant_id=? and status=1 and deleted=0", "store()", -1L, -1L); System.out.println(); for (String table : new String[] { "merchant_onboarding_application", "merchant_bank_card" }) { List declared = declaredColumns(source, table); List missing = new ArrayList<>(); for (String column : declared) if (!hasColumn(connection, database, table, column)) missing.add(column); System.out.println(table + ": initializer declares " + declared.size() + " columns, missing in this database = " + (missing.isEmpty() ? "none" : missing)); } } System.out.println(); System.out.println(red ? "RED: the endpoint's read path does not run against this database" : "GREEN: every statement on the endpoint's read path ran"); System.exit(red ? 1 : 0); } private static boolean run(Connection connection, String sql, String label, Object... arguments) { try (PreparedStatement statement = connection.prepareStatement(sql)) { for (int index = 0; index < arguments.length; index++) statement.setObject(index + 1, arguments[index]); try (ResultSet rows = statement.executeQuery()) { int count = 0; while (rows.next()) count++; System.out.println("ok " + label + " -> rows=" + count); return false; } } catch (SQLException exception) { System.out.println("FAIL " + label + " -> " + exception.getMessage()); return true; } } /** Column names the initializer adds to one table, in declaration order. */ private static List declaredColumns(String source, String table) throws Exception { String map = table.equals("merchant_bank_card") ? "bankCardColumns" : "columns"; Matcher matcher = Pattern.compile("\\b" + map + "\\.put\\(\"([a-z_]+)\"").matcher(Files.readString(Path.of(source))); List names = new ArrayList<>(); while (matcher.find()) names.add(matcher.group(1)); return names; } private static boolean exists(Connection connection, String database, String table) throws SQLException { try (PreparedStatement statement = connection.prepareStatement( "select count(*) from information_schema.tables where table_schema=? and table_name=?")) { statement.setString(1, database); statement.setString(2, table); try (ResultSet rows = statement.executeQuery()) { return rows.next() && rows.getInt(1) > 0; } } } private static boolean hasColumn(Connection connection, String database, String table, String column) throws SQLException { try (PreparedStatement statement = connection.prepareStatement( "select count(*) from information_schema.columns where table_schema=? and table_name=? and column_name=?")) { statement.setString(1, database); statement.setString(2, table); statement.setString(3, column); try (ResultSet rows = statement.executeQuery()) { return rows.next() && rows.getInt(1) > 0; } } } }