web service api
This commit is contained in:
parent
f24b8820b6
commit
c6d4ce1906
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Importing Alpine with node 18 docker image
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
# Add dependencies
|
||||||
|
RUN apk add git python3 make g++
|
||||||
|
|
||||||
|
# Installing restroom (all packages except sawroom)
|
||||||
|
RUN npx -y create-restroom@next restroom-mw --all --no-@restroom-mw/sawroom --no-@restroom-mw/planetmint
|
||||||
|
|
||||||
|
WORKDIR /restroom-mw
|
||||||
|
|
||||||
|
|
||||||
|
# Force old Express behavior (Express 4) so routes like "/api/*" work
|
||||||
|
RUN yarn remove express || true \
|
||||||
|
&& yarn add --exact express@4.21.2 \
|
||||||
|
&& rm -rf node_modules \
|
||||||
|
&& yarn install --force
|
||||||
|
|
||||||
|
|
||||||
|
# Configure restroom
|
||||||
|
# Set OPENAPI=false if you want to deactivate Swagger for production
|
||||||
|
ENV CUSTOM_404_MESSAGE="nothing to see here"
|
||||||
|
ENV HTTP_PORT=3300
|
||||||
|
ENV HTTPS_PORT=3301
|
||||||
|
ENV OPENAPI=true
|
||||||
|
ENV FILES_DIR=./contracts
|
||||||
|
ENV CHAIN_EXT=chain
|
||||||
|
ENV YML_EXT=yml
|
||||||
|
|
||||||
|
# Adding the exported files
|
||||||
|
RUN echo "Adding exported contracts from apiroom"
|
||||||
|
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Create the keypair from a name passed from data/keys\n\n# Here we load the identity of the executor\nGiven my name is in a 'string' named 'myName'\n\n# Here we generate and print the keypair\nWhen I create the ecdh key\nThen print my 'keyring'\n"> ./contracts/Generate-a-keypair,-reading-identity-from-data.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"myName\":\"User123456\"}""> ./contracts/Generate-a-keypair,-reading-identity-from-data.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Encrypt a message with the password \nGiven that I have a 'string' named 'password' \nGiven that I have a 'string' named 'header' \nGiven that I have a 'string' named 'message' \nWhen I encrypt the secret message 'message' with 'password' \nThen print the 'secret message'\n"> ./contracts/Encrypt-a-message-with-the-password.zen || true
|
||||||
|
|
||||||
|
RUN echo -e ""{}""> ./contracts/Encrypt-a-message-with-the-password.keys
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"header\":\"A very important secret\",\"message\":\"Dear Bob, your name is too short, goodbye - Alice.\",\"password\":\"myVerySecretPassword\"}""> ./contracts/Encrypt-a-message-with-the-password.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Decrypt the message with the password \nGiven that I have a valid 'secret message' \nGiven that I have a 'string' named 'password' \nWhen I decrypt the text of 'secret message' with 'password' \nWhen I rename the 'text' to 'textDecrypted' \nThen print the 'textDecrypted' as 'string'\n"> ./contracts/Decrypt-the-message-with-the-password.zen || true
|
||||||
|
|
||||||
|
RUN echo -e ""{}""> ./contracts/Decrypt-the-message-with-the-password.keys
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"secret_message\":{\"checksum\":\"76U+nWVZBwBMbOOktCnZug==\",\"header\":\"QSB2ZXJ5IGltcG9ydGFudCBzZWNyZXQ=\",\"iv\":\"R+B2z2pTLkMVGFCuFHnYL5sAIeuolYmgUOdMm2AOvTI=\",\"text\":\"Df8C8Kkd+ngVAi/tGUe905VPTwId4hv+iL31dgylkDaDumI3BpRO5bN1qKfSsBi2KOA=\"},\"password\":\"myVerySecretPassword\"}""> ./contracts/Decrypt-the-message-with-the-password.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Alice encrypts a message for Bob \n\nGiven that I am known as 'sender' \nGiven that I have my valid 'keyring' \nGiven that I have a valid 'public key' from 'reciever' \nGiven that I have a 'string' named 'message' \nGiven that I have a 'string' named 'header' \n\nWhen I encrypt the secret message of 'message' for 'reciever' \nWhen I rename the 'secret message' to 'secret' \n\nThen print the 'secret' \n\n"> ./contracts/Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"reciever\":{\"public_key\":\"BBA0kD35T9lUHR/WhDwBmgg/vMzlu1Vb0qtBjBZ8rbhdtW3AcX6z64a59RqF6FCV5q3lpiFNTmOgA264x1cZHE0=\"},\"message\":\"Dear Bob and Carl, if you are reading this, then we are not friend anymore. Goodbye.\",\"header\":\"Secret message for Bob and Carl\",\"sender\":{\"keyring\":{\"ecdh\":\"IStvfSREogWWYLB+DtpaSFqGJYMZMKvLIdGNN/H5DH4=\"}}}""> ./contracts/Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Bob decrypts the message from Alice \nGiven that I am known as 'reciever' \nGiven I have my 'keyring' \nGiven I have a 'public key' from 'sender' \nGiven I have a 'secret message' named 'secret' \nWhen I decrypt the text of 'secret' from 'sender' \nThen print the 'text' as 'string' \nThen print the 'header' from 'secret' as 'string'\n"> ./contracts/Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"sender\":{\"public_key\":\"BNRzlJ4csYlWgycGGiK/wgoEw3OizCdx9MWg06rxUBTP5rP9qPASOW5KY8YgmNjW5k7lLpboboHrsApWsvgkMN4=\"},\"secret\":{\"checksum\":\"sxoO1vewQmL8skCmfeiFgw==\",\"header\":\"U2VjcmV0IG1lc3NhZ2UgZm9yIEJvYiBhbmQgQ2FybA==\",\"iv\":\"AngaB+wTbAKWFDayWE2yWVSDD1f/w+lI+LkV0B8tIyM=\",\"text\":\"S2+pJNXhLgT46/ztk/XAJOWdl3jWR4svI170Me38bWHmvS3+kqZxkW2GIZJiw4C4GkdJ8MM2lvQJcP/GWM/7k+mc/XQoxI86Yu4RgCPqYJ+sKD0=\"},\"reciever\":{\"keyring\":{\"ecdh\":\"psBF05iHz/X8WBpwitJoSsZ7BiKawrdaVfQN3AtTa6I=\"}}}""> ./contracts/Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography.data
|
||||||
|
RUN echo -e "\nrule check version 3.0.0 \nScenario 'ecdh': Bob verifies the signature from Alice \n\n\n# Here we load the pubkey we'll verify the signature against\nGiven I have a 'public key' from 'signer' \n\n# Here we load the objects to be verified\nGiven I have a 'string' named 'myMessage' \n\n# Here we load the objects's signatures\nGiven I have a 'signature' named 'myMessage.signature' \n\n# Here we perform the verifications\nWhen I verify the 'myMessage' has a ecdh signature in 'myMessage.signature' by 'signer' \n\n# Here we print out the result: if the verifications succeeded, a string will be printed out\n# if the verifications failed, Zenroom will throw an error\nThen print the string 'Zenroom certifies that signature is correct!' \nThen print the 'myMessage' \n"> ./contracts/Verify-asymmetric-cryptography-signature.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"myMessage\":\"Dear Bob, your name is too short, goodbye - Alice.\",\"myMessage.signature\":{\"r\":\"vWerszPubruWexUib69c7IU8Dxy1iisUmMGC7h7arDw=\",\"s\":\"nSjxT+JAP56HMRJjrLwwB6kP+mluYySeZcG8JPBGcpY=\"},\"signer\":{\"public_key\":\"BBCQg21VcjsmfTmNsg+I+8m1Cm0neaYONTqRnXUjsJLPa8075IYH+a9w2wRO7rFM1cKmv19Igd7ntDZcUvLq3xI=\"}}""> ./contracts/Verify-asymmetric-cryptography-signature.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': create the signature of an object \nGiven I am 'signer' \nGiven I have my 'keyring' \nGiven that I have a 'string' named 'myMessage' inside 'mySecretStuff' \n\n# Here we are creating 3 signatures and renaming them afterwards, once with a string,\n# once with an array and once with a complex object such as the keypair\n# a signature is a schema containing two base64 key-values: 'r' and 's', read more about ECDSA at \n# https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm\n\nWhen I create the ecdh signature of 'myMessage' \nWhen I rename the 'ecdh signature' to 'myMessage.signature' \n\n# Here we are printing out the signatures \n\nThen print the 'myMessage' \nThen print the 'myMessage.signature' \n\n"> ./contracts/Sign-objects-using-asymmetric-cryptography.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"mySecretStuff\":{\"myMessage\":\"Dear Bob, your name is too short, goodbye - Alice.\"},\"signer\":{\"keyring\":{\"ecdh\":\"mukeqwntoJPtAN94jgahUA/ID7NptMLNL84sMPJ++eY=\"}}}""> ./contracts/Sign-objects-using-asymmetric-cryptography.data
|
||||||
|
RUN echo -e "\n\n# Loading scenarios\nScenario 'ecdh': Create the public key\n\n# Loading the private keys\nGiven I have the 'keyring'\n\n# Generating the public keys\nWhen I create the ecdh public key\n\n\n# Here we pring all the output\nThen print the 'ecdh public key'\n\n"> ./contracts/Generate-public-key.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"keyring\":{\"ecdh\":\"tWJ3bc7SgFQmWghl2lLmitzSCtfFYws1P2x8UW0edhE=\"}}""> ./contracts/Generate-public-key.data
|
||||||
|
|
||||||
|
|
||||||
|
# yarn install and run
|
||||||
|
CMD yarn start
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Importing Alpine with node 18 docker image
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
# Add dependencies
|
||||||
|
RUN apk add git python3 make g++
|
||||||
|
|
||||||
|
# Installing restroom (all packages except sawroom)
|
||||||
|
RUN npx -y create-restroom@next restroom-mw --all --no-@restroom-mw/sawroom --no-@restroom-mw/planetmint
|
||||||
|
|
||||||
|
WORKDIR /restroom-mw
|
||||||
|
|
||||||
|
|
||||||
|
# Force old Express behavior (Express 4) so routes like "/api/*" work
|
||||||
|
RUN yarn remove express || true \
|
||||||
|
&& yarn add --exact express@4.21.2 \
|
||||||
|
&& rm -rf node_modules \
|
||||||
|
&& yarn install --force
|
||||||
|
|
||||||
|
|
||||||
|
# Configure restroom
|
||||||
|
# Set OPENAPI=false if you want to deactivate Swagger for production
|
||||||
|
ENV CUSTOM_404_MESSAGE="nothing to see here"
|
||||||
|
ENV HTTP_PORT=3300
|
||||||
|
ENV HTTPS_PORT=3301
|
||||||
|
ENV OPENAPI=true
|
||||||
|
ENV FILES_DIR=./contracts
|
||||||
|
ENV CHAIN_EXT=chain
|
||||||
|
ENV YML_EXT=yml
|
||||||
|
|
||||||
|
# Adding the exported files
|
||||||
|
RUN echo "Adding exported contracts from apiroom"
|
||||||
|
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Create the keypair from a name passed from data/keys\n\n# Here we load the identity of the executor\nGiven my name is in a 'string' named 'myName'\n\n# Here we generate and print the keypair\nWhen I create the ecdh key\nThen print my 'keyring'\n"> ./contracts/Generate-a-keypair,-reading-identity-from-data.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"myName\":\"User123456\"}""> ./contracts/Generate-a-keypair,-reading-identity-from-data.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Encrypt a message with the password \nGiven that I have a 'string' named 'password' \nGiven that I have a 'string' named 'header' \nGiven that I have a 'string' named 'message' \nWhen I encrypt the secret message 'message' with 'password' \nThen print the 'secret message'\n"> ./contracts/Encrypt-a-message-with-the-password.zen || true
|
||||||
|
|
||||||
|
RUN echo -e ""{}""> ./contracts/Encrypt-a-message-with-the-password.keys
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"header\":\"A very important secret\",\"message\":\"Dear Bob, your name is too short, goodbye - Alice.\",\"password\":\"myVerySecretPassword\"}""> ./contracts/Encrypt-a-message-with-the-password.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Decrypt the message with the password \nGiven that I have a valid 'secret message' \nGiven that I have a 'string' named 'password' \nWhen I decrypt the text of 'secret message' with 'password' \nWhen I rename the 'text' to 'textDecrypted' \nThen print the 'textDecrypted' as 'string'\n"> ./contracts/Decrypt-the-message-with-the-password.zen || true
|
||||||
|
|
||||||
|
RUN echo -e ""{}""> ./contracts/Decrypt-the-message-with-the-password.keys
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"secret_message\":{\"checksum\":\"76U+nWVZBwBMbOOktCnZug==\",\"header\":\"QSB2ZXJ5IGltcG9ydGFudCBzZWNyZXQ=\",\"iv\":\"R+B2z2pTLkMVGFCuFHnYL5sAIeuolYmgUOdMm2AOvTI=\",\"text\":\"Df8C8Kkd+ngVAi/tGUe905VPTwId4hv+iL31dgylkDaDumI3BpRO5bN1qKfSsBi2KOA=\"},\"password\":\"myVerySecretPassword\"}""> ./contracts/Decrypt-the-message-with-the-password.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Alice encrypts a message for Bob \n\nGiven that I am known as 'sender' \nGiven that I have my valid 'keyring' \nGiven that I have a valid 'public key' from 'reciever' \nGiven that I have a 'string' named 'message' \nGiven that I have a 'string' named 'header' \n\nWhen I encrypt the secret message of 'message' for 'reciever' \nWhen I rename the 'secret message' to 'secret' \n\nThen print the 'secret' \n\n"> ./contracts/Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"reciever\":{\"public_key\":\"BBA0kD35T9lUHR/WhDwBmgg/vMzlu1Vb0qtBjBZ8rbhdtW3AcX6z64a59RqF6FCV5q3lpiFNTmOgA264x1cZHE0=\"},\"message\":\"Dear Bob and Carl, if you are reading this, then we are not friend anymore. Goodbye.\",\"header\":\"Secret message for Bob and Carl\",\"sender\":{\"keyring\":{\"ecdh\":\"IStvfSREogWWYLB+DtpaSFqGJYMZMKvLIdGNN/H5DH4=\"}}}""> ./contracts/Encrypt-a-message-for-two-recipients-using-asymmetric-cryptography.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': Bob decrypts the message from Alice \nGiven that I am known as 'reciever' \nGiven I have my 'keyring' \nGiven I have a 'public key' from 'sender' \nGiven I have a 'secret message' named 'secret' \nWhen I decrypt the text of 'secret' from 'sender' \nThen print the 'text' as 'string' \nThen print the 'header' from 'secret' as 'string'\n"> ./contracts/Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"sender\":{\"public_key\":\"BNRzlJ4csYlWgycGGiK/wgoEw3OizCdx9MWg06rxUBTP5rP9qPASOW5KY8YgmNjW5k7lLpboboHrsApWsvgkMN4=\"},\"secret\":{\"checksum\":\"sxoO1vewQmL8skCmfeiFgw==\",\"header\":\"U2VjcmV0IG1lc3NhZ2UgZm9yIEJvYiBhbmQgQ2FybA==\",\"iv\":\"AngaB+wTbAKWFDayWE2yWVSDD1f/w+lI+LkV0B8tIyM=\",\"text\":\"S2+pJNXhLgT46/ztk/XAJOWdl3jWR4svI170Me38bWHmvS3+kqZxkW2GIZJiw4C4GkdJ8MM2lvQJcP/GWM/7k+mc/XQoxI86Yu4RgCPqYJ+sKD0=\"},\"reciever\":{\"keyring\":{\"ecdh\":\"psBF05iHz/X8WBpwitJoSsZ7BiKawrdaVfQN3AtTa6I=\"}}}""> ./contracts/Decrypt-a-message-for-two-recipients-using-asymmetric-cryptography.data
|
||||||
|
RUN echo -e "\nrule check version 3.0.0 \nScenario 'ecdh': Bob verifies the signature from Alice \n\n\n# Here we load the pubkey we'll verify the signature against\nGiven I have a 'public key' from 'signer' \n\n# Here we load the objects to be verified\nGiven I have a 'string' named 'myMessage' \n\n# Here we load the objects's signatures\nGiven I have a 'signature' named 'myMessage.signature' \n\n# Here we perform the verifications\nWhen I verify the 'myMessage' has a ecdh signature in 'myMessage.signature' by 'signer' \n\n# Here we print out the result: if the verifications succeeded, a string will be printed out\n# if the verifications failed, Zenroom will throw an error\nThen print the string 'Zenroom certifies that signature is correct!' \nThen print the 'myMessage' \n"> ./contracts/Verify-asymmetric-cryptography-signature.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"myMessage\":\"Dear Bob, your name is too short, goodbye - Alice.\",\"myMessage.signature\":{\"r\":\"vWerszPubruWexUib69c7IU8Dxy1iisUmMGC7h7arDw=\",\"s\":\"nSjxT+JAP56HMRJjrLwwB6kP+mluYySeZcG8JPBGcpY=\"},\"signer\":{\"public_key\":\"BBCQg21VcjsmfTmNsg+I+8m1Cm0neaYONTqRnXUjsJLPa8075IYH+a9w2wRO7rFM1cKmv19Igd7ntDZcUvLq3xI=\"}}""> ./contracts/Verify-asymmetric-cryptography-signature.data
|
||||||
|
RUN echo -e "\nScenario 'ecdh': create the signature of an object \nGiven I am 'signer' \nGiven I have my 'keyring' \nGiven that I have a 'string' named 'myMessage' inside 'mySecretStuff' \n\n# Here we are creating 3 signatures and renaming them afterwards, once with a string,\n# once with an array and once with a complex object such as the keypair\n# a signature is a schema containing two base64 key-values: 'r' and 's', read more about ECDSA at \n# https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm\n\nWhen I create the ecdh signature of 'myMessage' \nWhen I rename the 'ecdh signature' to 'myMessage.signature' \n\n# Here we are printing out the signatures \n\nThen print the 'myMessage' \nThen print the 'myMessage.signature' \n\n"> ./contracts/Sign-objects-using-asymmetric-cryptography.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"mySecretStuff\":{\"myMessage\":\"Dear Bob, your name is too short, goodbye - Alice.\"},\"signer\":{\"keyring\":{\"ecdh\":\"mukeqwntoJPtAN94jgahUA/ID7NptMLNL84sMPJ++eY=\"}}}""> ./contracts/Sign-objects-using-asymmetric-cryptography.data
|
||||||
|
RUN echo -e "\n\n# Loading scenarios\nScenario 'ecdh': Create the public key\n\n# Loading the private keys\nGiven I have the 'keyring'\n\n# Generating the public keys\nWhen I create the ecdh public key\n\n\n# Here we pring all the output\nThen print the 'ecdh public key'\n\n"> ./contracts/Generate-public-key.zen || true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RUN echo -e ""{\"keyring\":{\"ecdh\":\"tWJ3bc7SgFQmWghl2lLmitzSCtfFYws1P2x8UW0edhE=\"}}""> ./contracts/Generate-public-key.data
|
||||||
|
|
||||||
|
|
||||||
|
# yarn install and run
|
||||||
|
CMD yarn start
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,213 @@
|
||||||
|
# ca_api/app.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from ca_api.db import db_cursor
|
||||||
|
|
||||||
|
from ca_core import entity as entity_core
|
||||||
|
from ca_core import group_member as group_core
|
||||||
|
from ca_core import property as property_core
|
||||||
|
from ca_core import metadata as metadata_core
|
||||||
|
|
||||||
|
app = FastAPI(title="PKI CA API")
|
||||||
|
|
||||||
|
|
||||||
|
def bad_request(e: Exception) -> HTTPException:
|
||||||
|
return HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
class CreatorIn(BaseModel):
|
||||||
|
name: str = Field(min_length=1, max_length=100)
|
||||||
|
public_key: str = Field(min_length=1, max_length=300)
|
||||||
|
|
||||||
|
|
||||||
|
class EnrollPersonIn(BaseModel):
|
||||||
|
name: str = Field(min_length=1, max_length=100)
|
||||||
|
public_key: str = Field(min_length=1, max_length=300)
|
||||||
|
creator_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class CreateGroupIn(BaseModel):
|
||||||
|
name: str = Field(min_length=1, max_length=100)
|
||||||
|
public_key: str = Field(min_length=1, max_length=300)
|
||||||
|
creator_id: int
|
||||||
|
ca_reference: str = Field(min_length=1, max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class AddMemberIn(BaseModel):
|
||||||
|
group_id: int
|
||||||
|
member_id: int
|
||||||
|
role: str = Field(min_length=1, max_length=10)
|
||||||
|
|
||||||
|
|
||||||
|
class SetPropertyIn(BaseModel):
|
||||||
|
entity_id: int
|
||||||
|
property_name: str = Field(min_length=1, max_length=100)
|
||||||
|
validation_policy: Optional[str] = Field(default="default", max_length=19)
|
||||||
|
source: Optional[str] = Field(default=None, max_length=150)
|
||||||
|
|
||||||
|
|
||||||
|
class DeletePropertyIn(BaseModel):
|
||||||
|
entity_id: int
|
||||||
|
property_name: str = Field(min_length=1, max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class SetMetadataNameIn(BaseModel):
|
||||||
|
name: str = Field(min_length=1, max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class SetMetadataDefensePIn(BaseModel):
|
||||||
|
defense_p: bool
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health() -> Dict[str, Any]:
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Entities ----
|
||||||
|
|
||||||
|
@app.post("/creators", status_code=201)
|
||||||
|
def create_creator(payload: CreatorIn) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
creator_id = entity_core.insert_creator(cur, payload.name, payload.public_key)
|
||||||
|
return {"id": creator_id}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/persons", status_code=201)
|
||||||
|
def enroll_person(payload: EnrollPersonIn) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
person_id = entity_core.enroll_person(cur, payload.name, payload.public_key, payload.creator_id)
|
||||||
|
return {"id": person_id}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/groups", status_code=201)
|
||||||
|
def create_group(payload: CreateGroupIn) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
group_id = entity_core.create_group(
|
||||||
|
cur,
|
||||||
|
payload.name,
|
||||||
|
payload.public_key,
|
||||||
|
payload.creator_id,
|
||||||
|
payload.ca_reference,
|
||||||
|
)
|
||||||
|
return {"id": group_id}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/entities/{entity_id}")
|
||||||
|
def get_entity(entity_id: int) -> Dict[str, Any]:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
row = entity_core.get_entity(cur, entity_id)
|
||||||
|
if row is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Entity not found")
|
||||||
|
return dict(row)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/entities/{entity_id}/status")
|
||||||
|
def set_entity_status(entity_id: int, status: str, changed_by: int) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
entity_core.set_entity_status(cur, entity_id, status, changed_by)
|
||||||
|
return {"ok": True}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Group members ----
|
||||||
|
|
||||||
|
@app.post("/groups/members", status_code=201)
|
||||||
|
def add_member(payload: AddMemberIn) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
group_core.add_group_member(cur, payload.group_id, payload.member_id, payload.role)
|
||||||
|
return {"ok": True}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/groups/{group_id}/members")
|
||||||
|
def list_members(group_id: int):
|
||||||
|
with db_cursor() as cur:
|
||||||
|
rows = group_core.get_members_of_group(cur, group_id)
|
||||||
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Properties ----
|
||||||
|
|
||||||
|
@app.post("/properties", status_code=201)
|
||||||
|
def set_property(payload: SetPropertyIn) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
property_core.set_property(
|
||||||
|
cur,
|
||||||
|
payload.entity_id,
|
||||||
|
payload.property_name,
|
||||||
|
validation_policy=payload.validation_policy or "default",
|
||||||
|
source=payload.source,
|
||||||
|
)
|
||||||
|
return {"ok": True}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/entities/{entity_id}/properties")
|
||||||
|
def get_properties(entity_id: int) -> Dict[str, Any]:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
props = property_core.get_properties(cur, entity_id)
|
||||||
|
return {"entity_id": entity_id, "properties": props}
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/properties")
|
||||||
|
def delete_property(payload: DeletePropertyIn) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
property_core.delete_property(cur, payload.entity_id, payload.property_name)
|
||||||
|
return {"ok": True}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Metadata ----
|
||||||
|
|
||||||
|
@app.get("/metadata")
|
||||||
|
def get_metadata() -> Dict[str, Any]:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
return {
|
||||||
|
"name": metadata_core.get_name(cur),
|
||||||
|
"comment": metadata_core.get_comment(cur),
|
||||||
|
"public_key": metadata_core.get_public_key(cur),
|
||||||
|
"defense_p": metadata_core.get_defense_p(cur),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/metadata/name")
|
||||||
|
def set_metadata_name(payload: SetMetadataNameIn) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
metadata_core.set_name(cur, payload.name)
|
||||||
|
return {"ok": True}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/metadata/defense_p")
|
||||||
|
def set_metadata_defense_p(payload: SetMetadataDefensePIn) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with db_cursor() as cur:
|
||||||
|
metadata_core.set_defense_p(cur, payload.defense_p)
|
||||||
|
return {"ok": True}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise bad_request(e)
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# ca_api/db.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
import psycopg
|
||||||
|
from psycopg.rows import dict_row
|
||||||
|
|
||||||
|
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:5432/ca")
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def db_cursor():
|
||||||
|
"""
|
||||||
|
Transaction-per-request.
|
||||||
|
Uses dict_row because ca_core expects dict-like rows (row["status"], etc.).
|
||||||
|
"""
|
||||||
|
conn = psycopg.connect(DATABASE_URL, row_factory=dict_row)
|
||||||
|
try:
|
||||||
|
with conn: # commits on success, rolls back on exception
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
yield cur
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,4 +1,4 @@
|
||||||
from db_logging import log_change
|
from .db_logging import log_change
|
||||||
|
|
||||||
|
|
||||||
def ensure_entity_active(cursor, entity_id):
|
def ensure_entity_active(cursor, entity_id):
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from db_logging import log_change
|
from .db_logging import log_change
|
||||||
from entity import ensure_entity_active
|
from .entity import ensure_entity_active
|
||||||
|
|
||||||
|
|
||||||
def _get_entity_type(cursor, entity_id):
|
def _get_entity_type(cursor, entity_id):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from db_logging import log_change
|
from .db_logging import log_change
|
||||||
|
|
||||||
|
|
||||||
def _ensure_singleton_row(cursor):
|
def _ensure_singleton_row(cursor):
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from db_logging import log_change
|
from .db_logging import log_change
|
||||||
from entity import ensure_entity_active
|
from .entity import ensure_entity_active
|
||||||
|
|
||||||
|
|
||||||
def _validate_validation_policy(validation_policy: str) -> str:
|
def _validate_validation_policy(validation_policy: str) -> str:
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -8,7 +8,7 @@ from pathlib import Path
|
||||||
code_path = Path(__file__).parents[1] / "ca_core"
|
code_path = Path(__file__).parents[1] / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
from crypto.zenroom_client import ZenroomDockerClient, ZenroomError
|
from ca_core.crypto.zenroom_client import ZenroomDockerClient, ZenroomError
|
||||||
|
|
||||||
|
|
||||||
def _docker_ok():
|
def _docker_ok():
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
||||||
code_path = Path(__file__).parent.parent.parent / "ca_core"
|
code_path = Path(__file__).parent.parent.parent / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
from crypto.zenroom_service_client import ZenroomServiceClient
|
from ca_core.crypto.zenroom_service_client import ZenroomServiceClient
|
||||||
|
|
||||||
|
|
||||||
def _live_enabled() -> bool:
|
def _live_enabled() -> bool:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
||||||
code_path = Path(__file__).parents[2] / "ca_core"
|
code_path = Path(__file__).parents[2] / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
from crypto.zenroom_service_client import ZenroomServiceClient
|
from ca_core.crypto.zenroom_service_client import ZenroomServiceClient
|
||||||
|
|
||||||
|
|
||||||
class TestZenroomServiceClientIntegration(unittest.TestCase):
|
class TestZenroomServiceClientIntegration(unittest.TestCase):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import psycopg
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
|
# Run these tests only when a real DB is provided.
|
||||||
|
DBURL = os.getenv("DATABASE_URL")
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
CREATE_TABLES = ROOT / "create_tables.sql"
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(DBURL, "DATABASE_URL not set; skipping API integration tests")
|
||||||
|
class TestApiIntegration(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
# Ensure the API uses the same DB as the tests.
|
||||||
|
os.environ["DATABASE_URL"] = DBURL
|
||||||
|
|
||||||
|
# Import after setting env so ca_api.db picks it up.
|
||||||
|
from ca_api.app import app # noqa: WPS433
|
||||||
|
|
||||||
|
cls.client = TestClient(app)
|
||||||
|
|
||||||
|
# Recreate tables from scratch for a clean slate.
|
||||||
|
schema_sql = CREATE_TABLES.read_text(encoding="utf-8")
|
||||||
|
with psycopg.connect(DBURL) as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(schema_sql)
|
||||||
|
|
||||||
|
def _log_count(self) -> int:
|
||||||
|
with psycopg.connect(DBURL) as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT COUNT(*) FROM log")
|
||||||
|
return int(cur.fetchone()[0])
|
||||||
|
|
||||||
|
def test_01_health(self):
|
||||||
|
r = self.client.get("/health")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
||||||
|
def test_02_creator_person_roundtrip_and_log(self):
|
||||||
|
before = self._log_count()
|
||||||
|
|
||||||
|
r = self.client.post("/creators", json={"name": "Alice", "public_key": "pk-alice"})
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
creator_id = r.json()["id"]
|
||||||
|
|
||||||
|
# Creating a creator should log at least one entry (your core logs mutations).
|
||||||
|
after = self._log_count()
|
||||||
|
self.assertGreaterEqual(after, before + 1)
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
"/persons",
|
||||||
|
json={"name": "Bob", "public_key": "pk-bob", "creator_id": creator_id},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
person_id = r.json()["id"]
|
||||||
|
|
||||||
|
r = self.client.get(f"/entities/{person_id}")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
body = r.json()
|
||||||
|
self.assertEqual(body["id"], person_id)
|
||||||
|
self.assertEqual(body["name"], "Bob")
|
||||||
|
self.assertEqual(body["type"], "person")
|
||||||
|
|
||||||
|
def test_03_group_create_add_member_list_members(self):
|
||||||
|
r = self.client.post("/creators", json={"name": "Admin", "public_key": "pk-admin"})
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
creator_id = r.json()["id"]
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
"/groups",
|
||||||
|
json={
|
||||||
|
"name": "Engineering",
|
||||||
|
"public_key": "pk-eng",
|
||||||
|
"creator_id": creator_id,
|
||||||
|
"ca_reference": "ca-ref-001",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
group_id = r.json()["id"]
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
"/persons",
|
||||||
|
json={"name": "Carol", "public_key": "pk-carol", "creator_id": creator_id},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
member_id = r.json()["id"]
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
"/groups/members",
|
||||||
|
json={"group_id": group_id, "member_id": member_id, "role": "member"},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
||||||
|
r = self.client.get(f"/groups/{group_id}/members")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
members = r.json()
|
||||||
|
self.assertTrue(any(m["member_id"] == member_id for m in members))
|
||||||
|
|
||||||
|
def test_04_property_set_get_delete(self):
|
||||||
|
r = self.client.post("/creators", json={"name": "PropAdmin", "public_key": "pk-pa"})
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
creator_id = r.json()["id"]
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
"/persons",
|
||||||
|
json={"name": "Dave", "public_key": "pk-dave", "creator_id": creator_id},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
entity_id = r.json()["id"]
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
"/properties",
|
||||||
|
json={
|
||||||
|
"entity_id": entity_id,
|
||||||
|
"property_name": "email",
|
||||||
|
"validation_policy": "default",
|
||||||
|
"source": "hr-system",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
||||||
|
r = self.client.get(f"/entities/{entity_id}/properties")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
props = r.json()["properties"]
|
||||||
|
self.assertIn("email", props)
|
||||||
|
|
||||||
|
r = self.client.request(
|
||||||
|
"DELETE",
|
||||||
|
"/properties",
|
||||||
|
json={"entity_id": entity_id, "property_name": "email"},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
||||||
|
r = self.client.get(f"/entities/{entity_id}/properties")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
props = r.json()["properties"]
|
||||||
|
self.assertNotIn("email", props)
|
||||||
|
|
||||||
|
def test_05_revoked_entity_is_immutable(self):
|
||||||
|
r = self.client.post("/creators", json={"name": "Revoker", "public_key": "pk-r"})
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
creator_id = r.json()["id"]
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
"/persons",
|
||||||
|
json={"name": "Eve", "public_key": "pk-eve", "creator_id": creator_id},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
eve_id = r.json()["id"]
|
||||||
|
|
||||||
|
# Revoke Eve
|
||||||
|
r = self.client.post(f"/entities/{eve_id}/status?status=revoked&changed_by={creator_id}")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
||||||
|
# Attempt to mutate a revoked entity (should be rejected by core rule)
|
||||||
|
r = self.client.post(
|
||||||
|
"/properties",
|
||||||
|
json={"entity_id": eve_id, "property_name": "email", "validation_policy": "default"},
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 400)
|
||||||
|
# detail message depends on your core exception text; just confirm it's a 400.
|
||||||
|
self.assertIn("detail", r.json())
|
||||||
|
|
||||||
|
def test_06_metadata_set_and_get(self):
|
||||||
|
r = self.client.post("/metadata/name", json={"name": "My CA"})
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
||||||
|
r = self.client.post("/metadata/defense_p", json={"defense_p": True})
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
||||||
|
r = self.client.get("/metadata")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
body = r.json()
|
||||||
|
self.assertEqual(body["name"], "My CA")
|
||||||
|
self.assertEqual(body["defense_p"], True)
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import unittest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from ca_api.app import app
|
||||||
|
|
||||||
|
|
||||||
|
class TestApiSmoke(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.client = TestClient(app)
|
||||||
|
|
||||||
|
def test_health(self):
|
||||||
|
r = self.client.get("/health")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import unittest
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from ca_api.app import app
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeCursor:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _fake_db_cursor():
|
||||||
|
# Mimics ca_api.db.db_cursor() which yields a cursor
|
||||||
|
yield _FakeCursor()
|
||||||
|
|
||||||
|
|
||||||
|
class TestApiUnit(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.client = TestClient(app)
|
||||||
|
|
||||||
|
def test_create_creator_201(self):
|
||||||
|
with patch("ca_api.app.db_cursor", _fake_db_cursor), \
|
||||||
|
patch("ca_api.app.entity_core.insert_creator", return_value=123) as m_insert:
|
||||||
|
payload = {"name": "Alice", "public_key": "pk-alice"}
|
||||||
|
r = self.client.post("/creators", json=payload)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
self.assertEqual(r.json(), {"id": 123})
|
||||||
|
|
||||||
|
# Ensure core called with (cursor, name, public_key)
|
||||||
|
args, kwargs = m_insert.call_args
|
||||||
|
self.assertIsInstance(args[0], _FakeCursor)
|
||||||
|
self.assertEqual(args[1], "Alice")
|
||||||
|
self.assertEqual(args[2], "pk-alice")
|
||||||
|
self.assertEqual(kwargs, {})
|
||||||
|
|
||||||
|
def test_get_entity_404(self):
|
||||||
|
with patch("ca_api.app.db_cursor", _fake_db_cursor), \
|
||||||
|
patch("ca_api.app.entity_core.get_entity", return_value=None):
|
||||||
|
r = self.client.get("/entities/99999")
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
self.assertEqual(r.json()["detail"], "Entity not found")
|
||||||
|
|
||||||
|
def test_get_entity_200(self):
|
||||||
|
# FastAPI returns dict(row), so return any mapping-like object here
|
||||||
|
with patch("ca_api.app.db_cursor", _fake_db_cursor), \
|
||||||
|
patch("ca_api.app.entity_core.get_entity", return_value={"id": 7, "name": "Bob"}):
|
||||||
|
r = self.client.get("/entities/7")
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.json()["id"], 7)
|
||||||
|
self.assertEqual(r.json()["name"], "Bob")
|
||||||
|
|
||||||
|
def test_set_property_400_on_value_error(self):
|
||||||
|
def _raise(*args, **kwargs):
|
||||||
|
raise ValueError("bad property")
|
||||||
|
|
||||||
|
with patch("ca_api.app.db_cursor", _fake_db_cursor), \
|
||||||
|
patch("ca_api.app.property_core.set_property", side_effect=_raise):
|
||||||
|
payload = {"entity_id": 1, "property_name": "email", "validation_policy": "default"}
|
||||||
|
r = self.client.post("/properties", json=payload)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 400)
|
||||||
|
self.assertEqual(r.json()["detail"], "bad property")
|
||||||
|
|
||||||
|
def test_add_member_201(self):
|
||||||
|
with patch("ca_api.app.db_cursor", _fake_db_cursor), \
|
||||||
|
patch("ca_api.app.group_core.add_group_member", return_value=None) as m_add:
|
||||||
|
payload = {"group_id": 10, "member_id": 20, "role": "admin"}
|
||||||
|
r = self.client.post("/groups/members", json=payload)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 201)
|
||||||
|
self.assertEqual(r.json(), {"ok": True})
|
||||||
|
|
||||||
|
args, kwargs = m_add.call_args
|
||||||
|
self.assertIsInstance(args[0], _FakeCursor)
|
||||||
|
self.assertEqual(args[1:], (10, 20, "admin"))
|
||||||
|
self.assertEqual(kwargs, {})
|
||||||
|
|
@ -6,7 +6,7 @@ import psycopg
|
||||||
code_path = Path(__file__).parent.parent / "ca_core"
|
code_path = Path(__file__).parent.parent / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
import entity
|
from ca_core import entity
|
||||||
|
|
||||||
DBNAME = "ca"
|
DBNAME = "ca"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import psycopg
|
||||||
code_path = Path(__file__).parent.parent / "ca_core"
|
code_path = Path(__file__).parent.parent / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
import entity
|
from ca_core import entity
|
||||||
import group_member
|
from ca_core import group_member
|
||||||
|
|
||||||
DBNAME = "ca"
|
DBNAME = "ca"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from crypto.zenroom_service_client import ZenroomServiceClient
|
from ca_core.crypto.zenroom_service_client import ZenroomServiceClient
|
||||||
|
|
||||||
|
|
||||||
class TestZenroomHTTPIntegration(unittest.TestCase):
|
class TestZenroomHTTPIntegration(unittest.TestCase):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import psycopg
|
||||||
code_path = Path(__file__).parent.parent / "ca_core"
|
code_path = Path(__file__).parent.parent / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
import metadata
|
from ca_core import metadata
|
||||||
|
|
||||||
DBNAME = "ca"
|
DBNAME = "ca"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import psycopg
|
||||||
code_path = Path(__file__).parent.parent / "ca_core"
|
code_path = Path(__file__).parent.parent / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
import entity
|
from ca_core import entity
|
||||||
import property
|
from ca_core import property
|
||||||
|
|
||||||
DBNAME = "ca"
|
DBNAME = "ca"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from unittest import mock
|
||||||
code_path = Path(__file__).parent.parent / "ca_core"
|
code_path = Path(__file__).parent.parent / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
from crypto.zenroom_client import ZenroomDockerClient, ZenroomError
|
from ca_core.crypto.zenroom_client import ZenroomDockerClient, ZenroomError
|
||||||
|
|
||||||
|
|
||||||
class TestZenroomDockerClient(unittest.TestCase):
|
class TestZenroomDockerClient(unittest.TestCase):
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
||||||
code_path = Path(__file__).parents[1] / "ca_core"
|
code_path = Path(__file__).parents[1] / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
from crypto.zenroom_service_client import ZenroomServiceClient, ZenroomServiceError
|
from ca_core.crypto.zenroom_service_client import ZenroomServiceClient, ZenroomServiceError
|
||||||
|
|
||||||
|
|
||||||
class _FakeHTTPResponse:
|
class _FakeHTTPResponse:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
||||||
code_path = Path(__file__).parents[1] / "ca_core"
|
code_path = Path(__file__).parents[1] / "ca_core"
|
||||||
sys.path.insert(0, str(code_path))
|
sys.path.insert(0, str(code_path))
|
||||||
|
|
||||||
from crypto.zenroom_service_client import ZenroomServiceClient
|
from ca_core.crypto.zenroom_service_client import ZenroomServiceClient
|
||||||
|
|
||||||
|
|
||||||
class _FakeHTTPResponse:
|
class _FakeHTTPResponse:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue