修复错误,优化性能,实现类型系统,优化安装卸载过程

This commit is contained in:
LIm1tZz7 committed 2026-10-05 00:15:01 +08:00
1 parent fd5f320360
commit 48eec484b7
104 files changed
+4127 -845

No files matched your search

+78 -1
View File
@@ -3,6 +3,8 @@
module Main (main) where
import ChuSQL.CLI.Format (OutputFormat (..), parseFormat, renderCsv, renderJson, renderTable)
import ChuSQL.CLI.History (historySettings, rememberStatement)
import ChuSQL.CLI.Password (readPasswordInput)
import ChuSQL.CLI.Script (Meta (..), errorHint, parseMeta, statementComplete, stripTerminator, takeStatement)
import ChuSQL.Core.Model (Value (..))
import ChuSQL.Core.Protocol (QueryResult (..), queryResultJson)
@@ -11,9 +13,15 @@ import qualified Data.Aeson as A
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Control.Exception (bracket, finally)
import Control.Monad.IO.Class (liftIO)
import System.Directory (getTemporaryDirectory, removeFile)
import System.IO (Handle, SeekMode (AbsoluteSeek), hClose, hPutStr, hSeek, hSetNewlineMode, noNewlineTranslation, openTempFile)
import Test.Hspec
import System.Console.Haskeline (getHistory, getInputLine, runInputT, runInputTBehavior, useFileHandle)
import System.Console.Haskeline.History (historyLines)
-- chusql-cli 测试:只覆盖本前端的纯逻辑(输出格式、语句缓冲与元命令)。
-- chusql-cli 测试:输出格式、语句缓冲、元命令与口令输入。
-- | 测试入口
main :: IO ()
@@ -24,6 +32,75 @@ spec :: Spec
spec = do
formatSpec
scriptSpec
passwordSpec
historySpec
-- | 历史过滤与持久化的用例
historySpec :: Spec
historySpec = describe "CLI: SQL history" $ do
it "does not automatically record unfinished input lines" $
withPasswordInput "CREATE USER alice IDENTIFIED BY\n'secret'\n" (\handle -> do
entries <- runInputTBehavior (useFileHandle handle) (historySettings Nothing) $ do
getInputLine "" >>= liftExpectation "CREATE USER alice IDENTIFIED BY"
getInputLine "" >>= liftExpectation "'secret'"
historyLines <$> getHistory
entries `shouldBe` [])
it "keeps ordinary statements and excludes multiline or mixed-case passwords" $ do
entries <- runInputT (historySettings Nothing) $ do
rememberStatement "SELECT 1;"
rememberStatement "CREATE USER alice\nWITH PaSsWoRd\n'secret';"
rememberStatement "ALTER USER alice PASSWORD 'new-secret';"
rememberStatement "CREATE USER alice\nIdEnTiFiEd /* credential */ BY\n'secret';"
rememberStatement "ALTER ROLE analyst IDENTIFIED BY 'new-secret';"
rememberStatement "SELECT 'password';"
rememberStatement " "
historyLines <$> getHistory
entries `shouldBe` ["SELECT 1;"]
it "persists only safe statements from a batch" $ do
directory <- getTemporaryDirectory
bracket (openTempFile directory "csql-history-test")
(\(path, _) -> removeFile path)
(\(path, handle) -> do
hClose handle
runInputT (historySettings (Just path)) $
rememberBatch "SELECT 1; CREATE USER alice IDENTIFIED BY 'secret'; SELECT 2;"
entries <- runInputT (historySettings (Just path)) (historyLines <$> getHistory)
entries `shouldBe` ["SELECT 2;", "SELECT 1;"])
where
-- | 核对实际读取的输入行
liftExpectation expected actual = liftIO (actual `shouldBe` Just expected)
-- | 按完整语句筛选批量输入
rememberBatch input = case takeStatement input of
Nothing -> pure ()
Just (statement, rest) -> do
rememberStatement (statement <> ";")
rememberBatch rest
-- | 在临时句柄中写入口令输入
withPasswordInput :: String -> (Handle -> IO a) -> IO a
withPasswordInput input action = do
directory <- getTemporaryDirectory
bracket (openTempFile directory "csql-password-test")
(\(path, handle) -> hClose handle `finally` removeFile path)
(\(_, handle) -> do
hSetNewlineMode handle noNewlineTranslation
hPutStr handle input
hSeek handle AbsoluteSeek 0
action handle)
-- | 管道口令的行尾与内容用例
passwordSpec :: Spec
passwordSpec = describe "CLI: password input" $ do
it "reads LF, CRLF and unterminated passwords identically" $ do
mapM_ (\input -> withPasswordInput input (\handle -> readPasswordInput handle `shouldReturn` "secret"))
["secret\n", "secret\r\n", "secret"]
it "preserves password spaces and internal carriage returns" $
withPasswordInput " a\rb \r\n" (\handle -> readPasswordInput handle `shouldReturn` " a\rb ")
it "accepts an empty password and consumes only one line" $
withPasswordInput "\r\nsecond\n" (\handle -> do
readPasswordInput handle `shouldReturn` ""
readPasswordInput handle `shouldReturn` "second"
readPasswordInput handle `shouldReturn` "")
-- | 输出格式的用例
formatSpec :: Spec