修复bug

This commit is contained in:
LIm1tZz7 committed 2026-10-11 17:32:12 +08:00
1 parent fc9bbc20e4
commit e87c4ee6a8
90 files changed
+3828 -452

No files matched your search

+96
View File
@@ -946,6 +946,102 @@ roleSpec env = describe "roles and grants" $ do
querySpec :: AppEnv -> Spec
querySpec env = describe "the SQL console" $ do
withApp (webApp env) $ do
it "analyzes explicit declarations for SELECT users and rejects stale schemas" $ do
admin <- adminHeaders
ensureDatabase admin testDatabaseName
ensureAccount admin "normalization_web_reader" "Reader-Secret-1234"
created <- testSql admin "CREATE TABLE normalization_web (id INT, course INT, name TEXT)"
expectStatusWith "normalization table" created 200
let declaration = object ["declared_keys" .= ([["id", "course"]] :: [[Text]]),
"dependencies" .= [object ["lhs" .= (["id"] :: [Text]), "rhs" .= (["name"] :: [Text])]],
"dependencies_complete" .= True, "atomic_attributes" .= True]
payload = object ["api_version" .= (1 :: Int), "database" .= testDatabaseName,
"table" .= ("normalization_web" :: Text), "declaration" .= declaration]
unauthorized <- postAs (withDb testDatabaseName []) "/api/normalization/analyze" (encode payload)
expectStatusWith "anonymous normalization" unauthorized 401
reader <- ordinaryHeaders "normalization_web_reader" "Reader-Secret-1234"
denied <- postAs (withDb testDatabaseName reader) "/api/normalization/analyze" (encode payload)
expectStatusWith "SELECT required" denied 403
granted <- testSql admin "GRANT SELECT ON normalization_web TO normalization_web_reader"
expectStatusWith "normalization SELECT" granted 200
readerAgain <- ordinaryHeaders "normalization_web_reader" "Reader-Secret-1234"
report <- postAs (withDb testDatabaseName readerAgain) "/api/normalization/analyze" (encode payload)
expectStatusWith "conditional report" report 200
check (at "conditional" (jsonBody report) `shouldBe` A.Bool True)
check (map (asText . at "verdict") (items (at "findings" (jsonBody report))) `shouldBe` ["satisfied", "violated", "violated", "violated"])
changed <- testSql admin "ALTER TABLE normalization_web ADD COLUMN extra INT"
expectStatusWith "change normalization schema" changed 200
let stale = object ["api_version" .= (1 :: Int), "database" .= testDatabaseName,
"table" .= ("normalization_web" :: Text), "declaration" .= declaration,
"expected_object" .= at "object" (jsonBody report), "expected_schema_fingerprint" .= at "schema_fingerprint" (jsonBody report)]
conflicted <- postAs (withDb testDatabaseName readerAgain) "/api/normalization/analyze" (encode stale)
expectStatusWith "schema changed" conflicted 409
it "uses a matching editing snapshot without creating a second transaction" $ do
admin <- adminHeaders
ensureDatabase admin testDatabaseName
created <- testSql admin "CREATE TABLE normalization_edit (id INT, value INT)"
expectStatusWith "normalization snapshot table" created 200
begun <- editRequest admin "/api/edit/begin" "normalization-owner" []
expectStatusWith "normalization edit begins" begun 200
changed <- testSql admin "ALTER TABLE normalization_edit ADD COLUMN extra INT"
expectStatusWith "live schema changes" changed 200
let payload = object ["api_version" .= (1 :: Int), "database" .= testDatabaseName,
"table" .= ("normalization_edit" :: Text), "declaration" .= object []]
snapshot <- postAs (("X-ChuSQL-Edit", "normalization-owner") : withDb testDatabaseName admin) "/api/normalization/analyze" (encode payload)
expectStatusWith "analysis on edit connection" snapshot 200
check (at "view" (jsonBody snapshot) `shouldBe` A.String "transaction_snapshot")
check (length (items (at "schema" (jsonBody snapshot))) `shouldBe` 2)
forged <- postAs (("X-ChuSQL-Edit", "another-page") : withDb testDatabaseName admin) "/api/normalization/analyze" (encode payload)
expectStatusWith "page identity mismatch" forged 409
rolledBack <- editRequest admin "/api/edit/rollback" "normalization-owner" []
expectStatusWith "release edit snapshot" rolledBack 200
it "binds normalization to the owning SQL console transaction" $ do
admin <- adminHeaders
ensureDatabase admin testDatabaseName
created <- testSql admin "CREATE TABLE normalization_console (id INT)"
expectStatusWith "console normalization table" created 200
let owner = ("X-ChuSQL-Console", "normalization-console") : admin
payload = object ["api_version" .= (1 :: Int), "database" .= testDatabaseName,
"table" .= ("normalization_console" :: Text), "declaration" .= object []]
begun <- testSql owner "BEGIN"
expectStatusWith "console begins" begun 200
snapshot <- postAs (withDb testDatabaseName owner) "/api/normalization/analyze" (encode payload)
expectStatusWith "owner console normalization" snapshot 200
check (at "view" (jsonBody snapshot) `shouldBe` A.String "transaction_snapshot")
denied <- postAs (withDb testDatabaseName admin) "/api/normalization/analyze" (encode payload)
expectStatusWith "unrelated console refused" denied 409
rolledBack <- testSql owner "ROLLBACK"
expectStatusWith "console ends" rolledBack 200
it "rejects duplicate declarations and oversized normalization bodies" $ do
admin <- adminHeaders
ensureDatabase admin testDatabaseName
duplicate <- postAs (withDb testDatabaseName admin) "/api/normalization/analyze"
"{\"api_version\":1,\"database\":\"test\",\"table\":\"users\",\"declaration\":{\"atomic_attributes\":true,\"atomic_attributes\":false}}"
expectStatusWith "duplicate declaration fields" duplicate 400
oversized <- postAs (withDb testDatabaseName admin) "/api/normalization/analyze" (BL.replicate 65537 32)
expectStatusWith "normalization request bound" oversized 413
it "exposes DISTINCT and self grants after grant option revocation" $ do
admin <- adminHeaders
ensureDatabase admin testDatabaseName
ensureAccount admin "http_option_reader" "Reader-Secret-1234"
created <- testSql admin "CREATE TABLE http_distinct_probe (id INT, value INT)"
expectStatusWith "distinct table" created 200
inserted <- testSql admin "INSERT INTO http_distinct_probe (id,value) VALUES (1,7),(2,7),(3,8)"
expectStatusWith "distinct rows" inserted 200
granted <- testSql admin "GRANT SELECT ON http_distinct_probe TO http_option_reader WITH GRANT OPTION"
expectStatusWith "grant option" granted 200
revoked <- testSql admin "REVOKE GRANT OPTION FOR SELECT ON http_distinct_probe FROM http_option_reader"
expectStatusWith "revoke only grant option" revoked 200
reader <- ordinaryHeaders "http_option_reader" "Reader-Secret-1234"
result <- testSql reader "SELECT DISTINCT value FROM http_distinct_probe ORDER BY 1 DESC LIMIT 2"
expectStatusWith "distinct output" result 200
check (at "rows" (jsonBody result) `shouldBe` A.toJSON ([[8], [7]] :: [[Int]]))
grants <- testSql reader "SHOW GRANTS"
expectStatusWith "own grants" grants 200
check (at "columns" (jsonBody grants) `shouldBe` A.toJSON (["identity", "privilege", "object", "grant_option"] :: [Text]))
denied <- testSql reader "GRANT SELECT ON http_distinct_probe TO admin"
expectStatusWith "no delegation" denied 403
it "returns typed compound values through HTTP and TCP" $ do
hs <- adminHeaders
ensureDatabase hs testDatabaseName
+50 -1
View File
@@ -12,7 +12,7 @@ globalThis.document = { getElementById: (id) => id === 'root' ? { addEventListen
let source = await readFile(new URL('../static/app.js', import.meta.url), 'utf8');
source = source.replaceAll("'./api.js'", JSON.stringify(new URL('../static/api.js', import.meta.url).href))
.replaceAll("'./core.js'", JSON.stringify(new URL('../static/core.js', import.meta.url).href))
.replace('void boot();', 'export { state, commitAll, rollbackAll, stageUpdate, removeStaged, ensureEdit, runSql };');
.replace('void boot();', 'export { state, commitAll, rollbackAll, stageUpdate, removeStaged, ensureEdit, runSql, renderNormalization };');
const app = await import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`);
let calls;
let commitResponse;
@@ -173,3 +173,52 @@ test('edits are locked while SQL releases the previous snapshot', async () => {
assert.equal(app.state.editBusy, false);
assert.equal(app.state.edit, null);
});
// 校验分析复用编辑视图并保留严格声明的原始字段。
test('normalization keeps snapshot ownership, exact identities and duplicate declaration fields', async () => {
await api.beginEdit('normalization-page', 'test');
const expected = { object: { database_id: '9007199254740993', object_id: '18446744073709551615' }, schema_fingerprint: 'schema' };
globalThis.fetch = async (url, init) => {
calls.push({ url, init });
return response({ api_version: 1, findings: [], schema: [] });
};
await api.analyzeNormalization('test', 'users', '{"atomic_attributes":true,"atomic_attributes":false}', expected);
const sent = calls.find((call) => call.url === '/api/normalization/analyze');
assert.equal(new Headers(sent.init.headers).get('X-ChuSQL-Edit'), 'normalization-page');
assert.equal(new Headers(sent.init.headers).get('X-ChuSQL-Database'), 'test');
assert.equal((sent.init.body.match(/"atomic_attributes"/g) ?? []).length, 2);
assert.deepEqual(JSON.parse(sent.init.body).expected_object, expected.object);
assert.equal(calls.filter((call) => call.url === '/api/edit/begin').length, 1);
api.forgetEdit();
globalThis.fetch = async (url, init) => {
calls.push({ url, init });
return response(url === '/api/query'
? { columns: [], rows: [], rowCount: 0, truncated: false, transaction: true }
: { api_version: 1, findings: [], schema: [] });
};
await api.query('BEGIN', 'test', 'normalization-console');
await api.analyzeNormalization('test', 'users', '{}', expected);
const fromConsole = calls.filter((call) => call.url === '/api/normalization/analyze').at(-1);
const begun = calls.filter((call) => call.url === '/api/query').at(-1);
assert.equal(new Headers(fromConsole.init.headers).get('X-ChuSQL-Console'), new Headers(begun.init.headers).get('X-ChuSQL-Console'));
assert.equal(new Headers(fromConsole.init.headers).get('X-ChuSQL-Edit'), null);
});
// 校验结构、违反证据和分解中的用户文字全部转义。
test('normalization escapes schema names, conditions, witnesses and suggestions', () => {
const result = { innerHTML: '' };
const original = document.getElementById;
document.getElementById = (id) => id === 'normalization-result' ? result : original(id);
try {
app.renderNormalization({ schema: [{ name: '<img src=x onerror=alert(1)>', ty: 'str', nullable: true }],
view: 'live_schema', candidate_keys: [['<script>']],
findings: [{ normal_form: 'bcnf', verdict: 'violated', condition: '<script>alert(1)</script>', witnesses: [{ lhs: ['<img>'], rhs: ['id'] }] }],
suggestions: [{ relations: [['<script>'], ['id']] }] });
assert.equal(result.innerHTML.includes('<img'), false);
assert.equal(result.innerHTML.includes('<script>'), false);
assert.equal(result.innerHTML.includes('&lt;img'), true);
assert.equal(result.innerHTML.includes('结构与约束'), true);
} finally {
document.getElementById = original;
}
});