Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PLT-5551 Unit Tests [PLT-5867 + PLT-5868] #76

Merged
merged 20 commits into from
Aug 15, 2023
Merged

PLT-5551 Unit Tests [PLT-5867 + PLT-5868] #76

merged 20 commits into from
Aug 15, 2023

Conversation

amnambiar
Copy link
Collaborator

@amnambiar amnambiar commented May 31, 2023

https://input-output.atlassian.net/browse/PLT-5551

Summary by CodeRabbit

Release Notes

  • New Feature: Added a new route CreateAuditorReport for creating auditor reports.
  • Refactor: Updated the Counter class in Python code to be thread-safe, preventing data races.
  • Test: Enhanced testing capabilities by adding data-testid attributes to various React components and introducing new test cases.
  • Bug Fix: Corrected the regular expression pattern in parseHTMLContents function to match closing </h2> tag correctly.

🎉 Celebrate this PR, it's no small feat,
New routes and tests, oh so neat!
Thread-safe counters, bugs retreat,
Code now stronger, can't be beat! 🚀

@coderabbitai: ignore

@amnambiar amnambiar changed the base branch from master to PLT-4386 August 2, 2023 07:54
@amnambiar amnambiar marked this pull request as ready for review August 8, 2023 08:47
@amnambiar
Copy link
Collaborator Author

There are some failures even at this point, but I would recommend to review the setup of the tests, and the way they are written for the time-being. As, most of them will definitely have to be rewritten once the revamp is in place.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2023

Image description CodeRabbit

Walkthrough

This pull request introduces a variety of changes across multiple files, primarily focusing on enhancing unit tests, improving thread safety in Python code, and refining the Plutus Certification module. The updates include adding data-testid attributes for testing in React components, implementing thread-safe operations in Python, and modifying functions and data types in Haskell for certification processes.

Changes

Files Summary
Python Code Review Fixed a race condition in multi-threaded Python code by introducing a lock to ensure thread-safety during counter increment operation.
react-web/src/pages/certification/components/*.tsx
react-web/src/test/customTestRenderHook.tsx
react-web/src/utils/utils.ts
Added data-testid attributes to facilitate testing in React components. Enhanced unit tests for various components and added a new function to render components with Redux store and React Router.
src/Plutus/Certification/*.hs Made significant modifications to the Plutus Certification module in Haskell. Changes include updating function signatures, adding new routes, modifying data types, and refining functions for handling metadata and certifications.

Comment on lines 1 to 152
"Cardano/Crypto/Encoding/Seed"
"Crypto/Math/Edwards25519"
"Crypto/Math/Bits"
"Crypto/Math/Bytes"
"Crypto/Math/NatMath"
"Crypto/ECC/Ed25519Donna"
"Crypto/ECC/Ed25519BIP32"
"Crypto/Encoding/BIP39"
"Crypto/Encoding/BIP39/Dictionary"
"Crypto/Encoding/BIP39/English"
"Cardano/Internal/Compat"
];
cSources = [ "cbits/ed25519/ed25519.c" "cbits/encrypted_sign.c" ];
hsSourceDirs = [ "src" ];
includeDirs = [ "cbits/ed25519" "cbits" ];
};
exes = {
"golden-tests" = {
depends = [
(hsPkgs."base" or (errorHandler.buildDepError "base"))
(hsPkgs."basement" or (errorHandler.buildDepError "basement"))
(hsPkgs."foundation" or (errorHandler.buildDepError "foundation"))
(hsPkgs."memory" or (errorHandler.buildDepError "memory"))
(hsPkgs."bytestring" or (errorHandler.buildDepError "bytestring"))
(hsPkgs."cryptonite" or (errorHandler.buildDepError "cryptonite"))
(hsPkgs."cardano-crypto" or (errorHandler.buildDepError "cardano-crypto"))
] ++ (pkgs.lib).optional (flags.golden-tests-exe) (hsPkgs."inspector" or (errorHandler.buildDepError "inspector"));
buildable = if flags.golden-tests-exe then true else false;
modules = [ "Test/Orphans" ];
hsSourceDirs = [ "test" ];
mainPath = [ "GoldenTest.hs" ] ++ [ "" ];
};
};
tests = {
"cardano-crypto-test" = {
depends = [
(hsPkgs."base" or (errorHandler.buildDepError "base"))
(hsPkgs."bytestring" or (errorHandler.buildDepError "bytestring"))
(hsPkgs."memory" or (errorHandler.buildDepError "memory"))
(hsPkgs."cryptonite" or (errorHandler.buildDepError "cryptonite"))
(hsPkgs."cardano-crypto" or (errorHandler.buildDepError "cardano-crypto"))
(hsPkgs."basement" or (errorHandler.buildDepError "basement"))
(hsPkgs."foundation" or (errorHandler.buildDepError "foundation"))
];
buildable = true;
modules = [
"Test/Crypto"
"Test/Crypto/Encoding"
"Test/Crypto/Encoding/BIP39"
"Test/Cardano"
"Test/Cardano/Crypto"
"Test/Cardano/Crypto/Encoding"
"Test/Cardano/Crypto/Encoding/Seed"
"Utils"
];
hsSourceDirs = [ "test" ];
mainPath = [ "Spec.hs" ];
};
"cardano-crypto-golden-tests" = {
depends = [
(hsPkgs."base" or (errorHandler.buildDepError "base"))
(hsPkgs."basement" or (errorHandler.buildDepError "basement"))
(hsPkgs."foundation" or (errorHandler.buildDepError "foundation"))
(hsPkgs."memory" or (errorHandler.buildDepError "memory"))
(hsPkgs."bytestring" or (errorHandler.buildDepError "bytestring"))
(hsPkgs."cryptonite" or (errorHandler.buildDepError "cryptonite"))
(hsPkgs."cardano-crypto" or (errorHandler.buildDepError "cardano-crypto"))
] ++ (pkgs.lib).optional (flags.golden-tests) (hsPkgs."inspector" or (errorHandler.buildDepError "inspector"));
buildable = if flags.golden-tests then true else false;
modules = [ "Test/Orphans" ];
hsSourceDirs = [ "test" ];
mainPath = [ "GoldenTest.hs" ];
};
};
benchmarks = {
"cardano-crypto-bench" = {
depends = [
(hsPkgs."base" or (errorHandler.buildDepError "base"))
(hsPkgs."bytestring" or (errorHandler.buildDepError "bytestring"))
(hsPkgs."memory" or (errorHandler.buildDepError "memory"))
(hsPkgs."cryptonite" or (errorHandler.buildDepError "cryptonite"))
(hsPkgs."cardano-crypto" or (errorHandler.buildDepError "cardano-crypto"))
(hsPkgs."gauge" or (errorHandler.buildDepError "gauge"))
];
buildable = true;
hsSourceDirs = [ "benchs" ];
};
};
};
} // {
src = (pkgs.lib).mkDefault (pkgs.fetchgit {
url = "0";
rev = "minimal";
sha256 = "";
}) // {
url = "0";
rev = "minimal";
sha256 = "";
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The url, rev, and sha256 fields in the src section (lines 143-150) are set to placeholder values. These should be updated with the correct repository URL, revision, and SHA-256 hash of the desired commit for the cardano-crypto package.

-       url = "0";
-       rev = "minimal";
-       sha256 = "";
+       url = "<correct-repo-url>";
+       rev = "<correct-commit-hash>";
+       sha256 = "<correct-sha256-hash>";

Comment on lines 40 to 62
data Status = Queued | Failed | Succeeded | ReadyForCertification | Certified | Aborted
deriving (Show, Read, Bounded, Enum, Eq, Generic)

instance ToJSON Status where
toJSON :: Status -> Value
toJSON Queued = toJSON ("queued" :: Text)
toJSON Failed = toJSON ("failed" :: Text)
toJSON Succeeded = toJSON ("succeeded" :: Text)
toJSON ReadyForCertification = toJSON ("ready-for-certification" :: Text)
toJSON Certified = toJSON ("certified" :: Text)
toJSON Aborted = toJSON ("aborted" :: Text)

instance FromJSON Status where
parseJSON =
withText "Status" handle
where
handle "queued" = pure Queued
handle "failed" = pure Failed
handle "succeeded" = pure Succeeded
handle "certified" = pure Succeeded
handle "ready-for-certification" = pure ReadyForCertification
handle "aborted" = pure Aborted
handle t = fail $ "provided text (" ++ show t ++ ") is not a Status"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The Status enum and its JSON serialization/deserialization methods are well implemented. However, there seems to be a mistake in the FromJSON instance for Status. In line 59, it should parse "certified" as Certified, not Succeeded.

- handle "certified" = pure Succeeded
+ handle "certified" = pure Certified

Comment on lines 82 to 88
instance ToSchema Status where
declareNamedSchema _ = do
let values = ["queued", "failed", "succeeded", "certified", "ready-for-certification","aborted"] :: [Value]
return $ NamedSchema (Just "RunStatus") $ mempty
& type_ ?~ SwaggerString
& enum_ ?~ values

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The Swagger schema for Status is correctly defined, but it would be better to generate the list of values programmatically from the Status type itself. This way, if new statuses are added or existing ones are removed, the Swagger schema will automatically stay up-to-date.

let values = map (toJSON . show) [minBound .. maxBound :: Status] :: [Value]

, ("reportContentId", textSchema)
]
& required .~ [ "runId", "created", "utcSchema", "repoUrl"
, "commitDate","commitHash", "runStatus", "certificationPrice"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

In the required fields list for the Swagger schema of Run, "utcSchema" is included, which seems like a mistake. It should probably be "syncedAt". Please verify this.

- , "commitDate","commitHash", "runStatus", "certificationPrice"]
+ , "commitDate","commitHash", "runStatus", "certificationPrice", "syncedAt"]

Comment on lines 143 to 144
instance IsLabel "profileId" (ID Profile -> Profile -> Profile) where
fromLabel v p = p { profileId = v}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The IsLabel instance for "profileId" seems unnecessary and could potentially introduce confusion. If you need to update the profileId of a Profile, consider using record syntax or lenses directly instead of OverloadedLabels.

@@ -685,6 +687,7 @@
"async".components.library.planned = lib.mkOverride 900 true;
"http-media".components.library.planned = lib.mkOverride 900 true;
"word8".components.library.planned = lib.mkOverride 900 true;
"http-conduit".components.library.planned = lib.mkOverride 900 true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The http-conduit library has been marked as planned. This means it will be built even if it's not required by any other package. Ensure that this is the intended behavior.

@@ -380,6 +220,7 @@ dapps = table "dapp"
createTables :: MonadSelda m => m ()
createTables = do
createTable certifications
createTable onChainCertifications
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

A new table onChainCertifications is being created. Ensure that this table's structure is defined correctly and that it meets your application's requirements.

@@ -389,3 +230,4 @@ createTables = do
createTable tiers
createTable tierFeatures
createTable subscriptions
createTable l1Certifications
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

A new table l1Certifications is being created. Ensure that this table's structure is defined correctly and that it meets your application's requirements.

Comment on lines 314 to 319
let cert = Certification def txId time
certId <- insertWithPK certifications [cert]
-- and now add a l1Certification
let l1Cert = L1Certification runId certId
_ <- insert l1Certifications [l1Cert]
pure $ Just (L1CertificationDTO l1Cert (#certId certId cert))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The logic of the createL1Certificate function has been modified. Now, after inserting a new certification, it also inserts a new L1Certification and returns an L1CertificationDTO. Ensure that this change aligns with the intended business logic and that the database schema supports these operations.

Comment on lines 303 to 317
, createAuditorReport = \dryRun reportInput (profileId,_) -> withEvent eb CreateAuditorReport \ev -> do
validateFeature L2UploadReport profileId
addField ev $ CreateAuditorReportFieldProfileId profileId
addField ev $ CreateAuditorReportDryRun (dryRun == Just True)
case dryRun of
Just True -> catch (createDraftMetadata reportInput) handleException
_ -> do
(fullMetadata,ipfs) <- catch (createMetadataAndPushToIpfs reportInput) handleException
addField ev $ CreateAuditorReportIpfsCid ipfs
pure fullMetadata
}
where
handleException :: (MonadError ServerError m ) => SomeException -> m a
handleException e = do
throwError err400 { errBody = LSB.pack $ show e }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

A new function createAuditorReport has been added. This function seems to handle exceptions, but it only catches SomeException, which might not be ideal as it will catch all exceptions, including asynchronous exceptions like ThreadKilled or UserInterrupt, which are usually better left unhandled. Consider catching specific exceptions that you expect can occur during the execution of your code.

-     handleException :: (MonadError ServerError m ) =>  SomeException -> m a
+     handleException :: (MonadError ServerError m ) =>  SpecificException -> m a

Comment on lines 44 to 60
hashURL :: MonadIO m => String -> m (Digest Blake2b_256)
hashURL url' = hashContent <$> simpleHttp (toHttpUrl url')

hashContent :: LBS.ByteString -> Digest Blake2b_256
hashContent = hashWith Blake2b_256 . LBS.toStrict

throwError :: String -> c
throwError = throw . userError

-- TODO: post to ipfs if it's the
toReport :: MonadIO m => [ReportURL] -> m Report
toReport [] = throwError "No report URLs provided"
toReport urls = do
xs <- mapM (hashURL . show . unReportURL) urls
if allSame xs
then pure $ Report urls ( digestToHash $ head xs)
else throwError "Report URLs do not match"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The function hashURL is used to hash the content of a URL. However, it does not handle potential exceptions that might occur during the HTTP request (e.g., network errors, timeouts). Consider adding error handling to ensure the function behaves correctly in case of failures.

hashURL :: MonadIO m => String -> m (Digest Blake2b_256)
hashURL url' = do
    result <- try $ simpleHttp (toHttpUrl url')
    case result of
        Left e -> throwError $ "Failed to fetch URL: " ++ show (e :: SomeException)
        Right content -> return $ hashContent content

Comment on lines 55 to 60
toReport [] = throwError "No report URLs provided"
toReport urls = do
xs <- mapM (hashURL . show . unReportURL) urls
if allSame xs
then pure $ Report urls ( digestToHash $ head xs)
else throwError "Report URLs do not match"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The function toReport throws an error if the hashes of the URLs are not all the same. This could lead to unexpected crashes if the input contains different URLs. It would be better to return an error type instead of throwing an exception.

toReport :: MonadIO m => [ReportURL] -> m (Either String Report)
toReport [] = return $ Left "No report URLs provided"
toReport urls = do
    xs <- mapM (hashURL . show . unReportURL) urls
    if allSame xs
        then return $ Right $ Report urls ( digestToHash $ head xs)
        else return $ Left "Report URLs do not match"

Comment on lines 150 to 188
offchain <- createOffchainMetadata input
finalOffchain <- addIpfsToMetadataIfNecessary offchain
ipfsCid <- uploadToIpfs finalOffchain
let onchain = createOnchainMetadata input $ Just (finalOffchain,[toMetadataUrl ipfsCid])
pure (FullMetadata (onchain,finalOffchain),IpfsCid ipfsCid)
where
addIpfsToMetadataIfNecessary (offchain :: OffChainMetadata) = do
let Report{reportURLs} = offchain.report
-- if there isn't any ipfs url in the report, add the ipfs url to the metadata
if Prelude.any (isIpfsUrl . unReportURL) reportURLs
then pure offchain
else do
-- get the ipfs cid of the first url
let url' = show . unReportURL . head $ offchain.report.reportURLs
bs <- simpleHttp (toHttpUrl url')
ipfsCid <- uploadToIpfsBS (LBS.toStrict bs)
let reportUrl = ReportURL $ parseURIUnsafe ("ipfs://" <> Text.unpack ipfsCid)
pure $ offchain { report = (offchain.report) { reportURLs = reportURLs <> [reportUrl] }}
isIpfsUrl uri = case uriScheme uri of
"ipfs:" -> True
_ -> False

toMetadataUrl :: Text -> MetadataUrl
toMetadataUrl ipfsCid = MetadataUrl $ parseURIUnsafe $ "ipfs://" <> Text.unpack ipfsCid

uploadToIpfs :: (MonadIO m, ToJSON a) => a -> m Text
uploadToIpfs obj = do
uploadToIpfsBS (LBS.toStrict $ encode obj)

uploadToIpfsBS :: (MonadIO m) => BS.ByteString -> m Text
uploadToIpfsBS bs = do
resp <- IPFS.uploadReportToIpfs IPFS.apiKey bs
case resp of
Left (IPFS.DecodeFailure _ err) -> throwError err
Left (IPFS.HttpError resp') ->
let (Status code msg) = responseStatus resp'
err = "IPFS gateway error code: " <> show code <> ", msg: " <> BS.unpack msg
in throwError err
Right (IPFS.UploadResponse (IpfsCid ipfsCid) _)-> pure ipfsCid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The function createMetadataAndPushToIpfs uploads data to IPFS but does not handle potential exceptions that might occur during the upload process (e.g., network errors, timeouts). Consider adding error handling to ensure the function behaves correctly in case of failures.

uploadToIpfsBS :: (MonadIO m) => BS.ByteString -> m (Either String Text)
uploadToIpfsBS bs = do
  result <- try $ IPFS.uploadReportToIpfs IPFS.apiKey bs
  case result of
    Left e -> return $ Left $ "Failed to upload to IPFS: " ++ show (e :: SomeException)
    Right resp ->
      case resp of
        Left (IPFS.DecodeFailure _ err) -> return $ Left err
        Left (IPFS.HttpError resp') ->
          let (Status code msg) = responseStatus resp'
              err = "IPFS gateway error code: " <> show code  <> ", msg: " <> BS.unpack msg
          in return $ Left err
        Right (IPFS.UploadResponse (IpfsCid ipfsCid) _) -> return $ Right ipfsCid

Comment on lines 68 to 70
parseURIUnsafe :: String -> URI
parseURIUnsafe = fromMaybe (throwError "Invalid URI") . parseURI

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The function parseURIUnsafe throws an error when it fails to parse a URI. It would be better to return an error type instead of throwing an exception.

parseURIUnsafe :: String -> Either String URI
parseURIUnsafe str = case parseURI str of
    Nothing -> Left "Invalid URI"
    Just uri -> Right uri

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

Commits Files that changed from the base of the PR and between bf67737 and c33d74a commits.
Files selected for processing (1)
  • react-web/src/pages/certification/components/SuccessCard.tsx (2 hunks)
Files skipped from review due to trivial changes (1)
  • react-web/src/pages/certification/components/SuccessCard.tsx
Review comments generated (0)
  • Actionable : 0
  • LGTM (Skipped): 0
  • Noise Suppression (Skipped): 0

Tips

Chat with Image description CodeRabbit Bot (@coderabbitai)

  • Tag the bot with @coderabbitai in any review comment to ask it questions. The bot will not respond unless explicitly tagged.
  • A review comment is a comment on a diff or a file and not a comment on the PR itself.
  • You can use the mouse to select multiple lines of code to provide exact context to the bot.

Code suggestions

  • The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
  • You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.

Pausing incremental reviews

  • Add @coderabbitai: ignore anywhere in the PR description to pause further reviews from the bot.

@RSoulatIOHK
Copy link
Collaborator

Image description CodeRabbit

Commits
Files selected for processing (1)
Files skipped from review due to trivial changes (1)
Review comments generated (0)
Tips

@coderabbitai: Can you review the file that you skipped?

Copy link
Collaborator

@RSoulatIOHK RSoulatIOHK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai Can you review the latest commit?

Base automatically changed from PLT-4386 to master August 11, 2023 16:00
@amnambiar
Copy link
Collaborator Author

Have couple test failures which will be taken care of during the revamp tickets. Merging this PR for now.

@amnambiar amnambiar merged commit 1fb261b into master Aug 15, 2023
4 of 6 checks passed
@amnambiar amnambiar deleted the PLT-5551 branch August 15, 2023 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants