{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} module Main (main) where import ChuSQL.CLI.Format (OutputFormat (..), parseFormat, renderCsv, renderJson, renderTable) import ChuSQL.CLI.Script (Meta (..), errorHint, parseMeta, statementComplete, stripTerminator, takeStatement) import ChuSQL.CLI.Session ( QueryResult (..), Session, authenticateSession, catalog, databases, newSession, queryResultJson, roleViews, runStatement, sessionDatabase, sessionIsAdmin, switchDatabase, ) import ChuSQL.Model (Database, Table (..), Value (..), pattern TInt, pattern TStr) import ChuSQL.Storage.IPC (TableInfo (..)) import ChuSQL.Web.Auth (Credential (..), hashPasswordWith) import ChuSQL.Web.Backend (memoryBackend) import ChuSQL.Web.Privileges (Grant (..), RoleView (..)) import Control.Concurrent.MVar (newMVar) import Data.Aeson (decode) import qualified Data.Aeson as A import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BL import Data.Either (isLeft, isRight) import Data.List (nub, sort) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Test.Hspec -- chusql-cli 测试:输出格式、交互层纯逻辑与共享账号会话。 main :: IO () main = hspec spec spec :: Spec spec = do formatSpec scriptSpec sessionSpec roleSpec formatSpec :: Spec formatSpec = describe "CLI: output formats" $ do it "renders a boxed table and marks NULL" $ renderTable sampleResult `shouldBe` T.unlines [ "+-----+--------+" , "| id | name |" , "+-----+--------+" , "| 1 | Alice |" , "| 2 | NULL |" , "+-----+--------+" , "(2 rows)" ] it "renders CSV with RFC 4180 quoting and empty NULL fields" $ renderCsv QueryResult { qrColumns = ["id", "name"] , qrRows = [[VInt 1, VStr "a,b"], [VNull, VStr "say \"hi\""]] , qrRowCount = 2 , qrTruncated = False , qrDatabase = Nothing } `shouldBe` "id,name\n1,\"a,b\"\n,\"say \"\"hi\"\"\"\n" it "renders JSON that parses back to the same result" $ (decode (BL.fromStrict (encodeUtf8 (renderJson sampleResult))) :: Maybe A.Value) `shouldBe` Just (queryResultJson sampleResult) it "accepts table/json/csv in any case and rejects anything else" $ do parseFormat "TABLE" `shouldBe` Just FormatTable parseFormat " json " `shouldBe` Just FormatJson parseFormat "csv" `shouldBe` Just FormatCsv parseFormat "xml" `shouldBe` Nothing scriptSpec :: Spec scriptSpec = do describe "CLI: statement buffering" $ do it "a statement is complete only at an unquoted semicolon" $ do statementComplete "SELECT 1;" `shouldBe` True statementComplete "SELECT 1" `shouldBe` False statementComplete "SELECT ';'" `shouldBe` False statementComplete "SELECT 'a''b'" `shouldBe` False statementComplete "SELECT 1 -- ;" `shouldBe` False statementComplete "SELECT /* ; */ 1" `shouldBe` False statementComplete "SELECT /* ; */ 1;" `shouldBe` True it "splits the first statement off and keeps the rest" $ takeStatement "SELECT 1; SELECT 2;" `shouldBe` Just ("SELECT 1", " SELECT 2;") it "drops the terminator before sending SQL to the server" $ do stripTerminator "SELECT 1 ; " `shouldBe` "SELECT 1" stripTerminator "SELECT 1" `shouldBe` "SELECT 1" it "suggests how to look around after a common error" $ do errorHint "unknown table: nope" `shouldBe` Just "run \\dt to list the tables of the current database" errorHint "unknown column: nope" `shouldBe` Just "run \\d