QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 7s
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Failing after 5s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
Checkpoint implementation at user-requested stop; document known failures and incomplete release qualification. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: GPT-6 Codex <noreply@openai.com>
191 lines
8.8 KiB
Java
191 lines
8.8 KiB
Java
/*
|
|
* QQQ - Low-code Application Framework for Engineers.
|
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
|
* contact@kingsrook.com
|
|
* https://github.com/Kingsrook/
|
|
*
|
|
* 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
|
|
*
|
|
* https://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.kingsrook.qbits.documentgeneration;
|
|
|
|
|
|
import java.sql.Connection;
|
|
import java.util.List;
|
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
|
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
|
import org.junit.jupiter.api.AfterAll;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.Test;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
|
|
|
|
/***************************************************************************
|
|
** Run the unchanged native authorization/transaction/concurrency suite on a
|
|
** real PostgreSQL 17 engine, plus PostgreSQL-specific durability and drift cases.
|
|
***************************************************************************/
|
|
class PostgresGenerationQueueTest extends GenerationQueueJdbcTest
|
|
{
|
|
private static PostgresGenerationDatabase postgres;
|
|
|
|
/***********************************************************************
|
|
** One exact disposable engine per suite; all test cases use new databases.
|
|
***********************************************************************/
|
|
@BeforeAll
|
|
static void startPostgres()
|
|
{
|
|
postgres = new PostgresGenerationDatabase();
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Remove only the fixture-owned engine after every inherited assertion.
|
|
***********************************************************************/
|
|
@AfterAll
|
|
static void stopPostgres()
|
|
{
|
|
if(postgres != null)
|
|
{
|
|
postgres.close();
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Native actions receive only the DML identity, never migration authority.
|
|
***********************************************************************/
|
|
@Override
|
|
protected RDBMSBackendMetaData database(String name, String defaultUrl) throws Exception
|
|
{
|
|
return (postgres.database(name));
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Assertion and fault-injection connections remain separate from QQQ.
|
|
***********************************************************************/
|
|
@Override
|
|
protected Connection connection() throws Exception
|
|
{
|
|
return (postgres.connection());
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Execute fixed fixture DDL, with no compatibility/emulation database mode.
|
|
***********************************************************************/
|
|
@Override
|
|
protected void sql(String source) throws Exception
|
|
{
|
|
postgres.sql(source);
|
|
}
|
|
|
|
/***********************************************************************
|
|
** Exercise the shipped native schema without the H2 timestamp rewrite.
|
|
***********************************************************************/
|
|
@Override
|
|
protected String schema(DocumentGenerationQBitConfig configuration)
|
|
{
|
|
return (DocumentGenerationSchema.postgresScript(configuration));
|
|
}
|
|
|
|
/***************************************************************************
|
|
** Cold server restart preserves committed results and untouched work.
|
|
***************************************************************************/
|
|
@Test
|
|
void serverRestartPreservesQueueAndIndependentAudit() throws Exception
|
|
{
|
|
int id = enqueue("restart");
|
|
var first = queue.advance(id);
|
|
assertThat(first.state()).isEqualTo("GENERATED");
|
|
postgres.restart();
|
|
assertThat(queue.status(id, -1, 10).items().getFirst()).isEqualTo(first);
|
|
assertThat(queue.advance(id).state()).isEqualTo("GENERATED");
|
|
assertThat(queue.advance(id)).isNull();
|
|
try(var connection = connection(); var statement = connection.createStatement();
|
|
var rows = statement.executeQuery("SELECT COUNT(*) FROM document_generation_event WHERE outcome='GENERATION_RETAINED'"))
|
|
{
|
|
rows.next();
|
|
assertThat(rows.getInt(1)).isEqualTo(2);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
** Exact text comparison keeps case and trailing-space keys distinct, and a retry is idempotent.
|
|
***************************************************************************/
|
|
@Test
|
|
void caseAndTrailingSpaceRemainDistinctIdempotencyKeys() throws Exception
|
|
{
|
|
int upper = enqueue("Case");
|
|
int lower = enqueue("case");
|
|
int spaced = enqueue("Case ");
|
|
assertThat(List.of(upper, lower, spaced)).doesNotHaveDuplicates();
|
|
assertThat(enqueue("Case")).isEqualTo(upper);
|
|
}
|
|
|
|
/***************************************************************************
|
|
** Unlogged tables, nondeterministic collations and missing idempotency keys fail closed before any write.
|
|
***************************************************************************/
|
|
@Test
|
|
void realSchemaDriftFailsClosed() throws Exception
|
|
{
|
|
sql("ALTER TABLE document_generation_stock SET UNLOGGED");
|
|
assertThatThrownBy(() -> enqueue("unlogged")).isInstanceOf(QException.class).hasMessageContaining("logged");
|
|
assertThatThrownBy(() -> new DocumentDefinitionService(config).createType("must not persist")).isInstanceOf(QException.class).hasMessageContaining("logged");
|
|
sql("ALTER TABLE document_generation_stock SET LOGGED");
|
|
sql("CREATE COLLATION fixture_ci (provider = icu, locale = 'und-u-ks-level2', deterministic = false)");
|
|
sql("ALTER TABLE document_generation_queue ALTER COLUMN request_key TYPE VARCHAR(130) COLLATE fixture_ci");
|
|
assertThatThrownBy(() -> enqueue("collation")).isInstanceOf(QException.class).hasMessageContaining("deterministic");
|
|
sql("ALTER TABLE document_generation_queue ALTER COLUMN request_key TYPE VARCHAR(130) COLLATE \"default\"");
|
|
sql("DROP INDEX uq_document_generation_queue_1");
|
|
assertThatThrownBy(() -> enqueue("index")).isInstanceOf(QException.class).hasMessageContaining("idempotency");
|
|
}
|
|
|
|
/***************************************************************************
|
|
** Role-level settings are observed from the actual server, never assumed from fixture setup.
|
|
***************************************************************************/
|
|
@Test
|
|
void weakCommitAndUnboundedLockSettingsAreRejectedBeforeWriting() throws Exception
|
|
{
|
|
try
|
|
{
|
|
sql("ALTER ROLE generation_app SET synchronous_commit='off'");
|
|
ConnectionManager.closeConnectionProviders();
|
|
assertThatThrownBy(() -> enqueue("weak-commit")).isInstanceOf(QException.class).hasMessageContaining("synchronous PostgreSQL");
|
|
sql("ALTER ROLE generation_app SET synchronous_commit='on'");
|
|
sql("ALTER ROLE generation_app RESET lock_timeout");
|
|
ConnectionManager.closeConnectionProviders();
|
|
assertThatThrownBy(() -> enqueue("unbounded-lock")).isInstanceOf(QException.class).hasMessageContaining("lock_timeout");
|
|
sql("ALTER ROLE generation_app SET lock_timeout='60s'");
|
|
ConnectionManager.closeConnectionProviders();
|
|
assertThatThrownBy(() -> enqueue("long-lock")).isInstanceOf(QException.class).hasMessageContaining("lock_timeout");
|
|
}
|
|
finally
|
|
{
|
|
sql("ALTER ROLE generation_app SET synchronous_commit='on'");
|
|
sql("ALTER ROLE generation_app SET lock_timeout='5s'");
|
|
ConnectionManager.closeConnectionProviders();
|
|
}
|
|
assertThat(queue.status(enqueue("restored"), -1, 10).items()).hasSize(2);
|
|
}
|
|
|
|
/***************************************************************************
|
|
** The native identity must be the postgres module on a real PostgreSQL17 server, not a vendor label.
|
|
***************************************************************************/
|
|
@Test
|
|
void vendorLabelWithoutPostgresModuleIsRejected() throws Exception
|
|
{
|
|
jdbcBackend.setBackendType("rdbms");
|
|
assertThatThrownBy(() -> enqueue("wrong-module")).isInstanceOf(QException.class).hasMessageContaining("not qualified");
|
|
}
|
|
}
|