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

Issue Credential v1 and Present Proof v1 #1056

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions issue_credential/.devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/python-3/.devcontainer/base.Dockerfile
ARG VARIANT="3.12"
FROM mcr.microsoft.com/devcontainers/python:${VARIANT}

ARG POETRY_VERSION="1.8.3"
ENV POETRY_HOME="/opt/poetry" \
POETRY_VERSION=${POETRY_VERSION}

RUN curl -sSL https://install.python-poetry.org | python3 - \
&& update-alternatives --install /usr/local/bin/poetry poetry /opt/poetry/bin/poetry 900 \
# Enable tab completion for bash
&& poetry completions bash >> /home/vscode/.bash_completion \
# Enable tab completion for Zsh
&& mkdir -p /home/vscode/.zfunc/ \
&& poetry completions zsh > /home/vscode/.zfunc/_poetry \
&& echo "fpath+=~/.zfunc\nautoload -Uz compinit && compinit" >> /home/vscode/.zshrc

COPY pyproject.toml ./
# COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.create true \
&& poetry install --no-root --no-interaction --with integration --extras "aca-py" \
&& rm -rf /root/.cache/pypoetry
59 changes: 59 additions & 0 deletions issue_credential/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "issue_credential",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
"VARIANT": "3.12-bullseye",
"POETRY_VERSION": "1.8.3"
}
},
"customizations": {
"vscode": {
"extensions": ["ms-python.python", "ms-python.vscode-pylance"],
"settings": {
"python.testing.pytestArgs": ["./issue_credential", "--no-cov"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.pytestPath": "pytest",
"editor.defaultFormatter": null,
"editor.formatOnSave": false, // enable per language
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true
},
"editor.defaultFormatter": "charliermarsh.ruff",
"ruff.organizeImports": true
},
"ruff.codeAction.fixViolation": {
"enable": true
},
"ruff.fixAll": true,
"ruff.format.args": ["--config=./pyproject.toml"],
"ruff.lint.args": ["--config=./pyproject.toml"]
}
}
},

"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"moby": false
}
},

// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",

"remoteEnv": {
"RUST_LOG": "aries-askar::log::target=error"
},

"mounts": [],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000, 3001],
"postCreateCommand": "bash ./.devcontainer/post-install.sh"
}
14 changes: 14 additions & 0 deletions issue_credential/.devcontainer/post-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
set -ex

# Convenience workspace directory for later use
WORKSPACE_DIR=$(pwd)

# install all ACA-Py requirements
python -m pip install --upgrade pip

# install black for formatting
pip3 install black

# Generate Poetry Lock file
poetry lock --no-update
9 changes: 9 additions & 0 deletions issue_credential/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# < Replace With The Plugin Name >

## Description

< Replace with information about the reason this plugin was produced and a brief overview of the features >

## Configuration

< Replace this section with an outline of configuration options and basic defaults for deploying the plugin >
31 changes: 31 additions & 0 deletions issue_credential/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM python:3.9-slim AS base
WORKDIR /usr/src/app

# Install and configure poetry
USER root

ENV POETRY_VERSION=1.7.1
ENV POETRY_HOME=/opt/poetry
RUN apt-get update && apt-get install -y curl && apt-get clean
RUN curl -sSL https://install.python-poetry.org | python -

ENV PATH="/opt/poetry/bin:$PATH"
RUN poetry config virtualenvs.in-project true

# Setup project
RUN mkdir issue_credential && touch issue_credential/__init__.py
COPY pyproject.toml poetry.lock README.md ./
ARG install_flags='--with integration --extras aca-py'
RUN poetry install ${install_flags}
USER $user

FROM python:3.9-bullseye
WORKDIR /usr/src/app
COPY --from=base /usr/src/app/.venv /usr/src/app/.venv
ENV PATH="/usr/src/app/.venv/bin:$PATH"

COPY issue_credential/ issue_credential/
COPY docker/*.yml ./

ENTRYPOINT ["/bin/bash", "-c", "aca-py \"$@\"", "--"]
CMD ["start", "--arg-file", "default.yml"]
22 changes: 22 additions & 0 deletions issue_credential/docker/default.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
label: issue_credential

admin: [0.0.0.0, 3001]
admin-insecure-mode: false
admin-api-key: change-me

inbound-transport:
- [http, 0.0.0.0, 3000]
- [ws, 0.0.0.0, 3002]
outbound-transport: http
endpoint:
- http://host.docker.internal:3000

plugin:
- issue_credential.v1_0

genesis-url: http://test.bcovrin.vonx.io/genesis

log-level: info

auto-accept-invites: true
auto-respond-messages: true
20 changes: 20 additions & 0 deletions issue_credential/docker/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
label: issue_credential

admin: [0.0.0.0, 3001]
admin-insecure-mode: true

inbound-transport:
- [http, 0.0.0.0, 3000]
outbound-transport: http
endpoint:
- http://host.docker.internal:3000

plugin:
- issue_credential.v1_0

genesis-url: http://test.bcovrin.vonx.io/genesis

log-level: info

auto-accept-invites: true
auto-respond-messages: true
20 changes: 20 additions & 0 deletions issue_credential/integration/Dockerfile.test.runner
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.9-slim
WORKDIR /usr/src/app

# install poetry
RUN pip3 install --no-cache-dir poetry

# Add docker-compose-wait tool
ENV WAIT_VERSION 2.7.2
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/$WAIT_VERSION/wait /wait
RUN chmod +x /wait

# install dependencies
COPY pyproject.toml .
COPY poetry.lock .
RUN poetry install --only main

# add tests to image
COPY tests/* tests/

ENTRYPOINT ["/bin/sh", "-c", "/wait && poetry run pytest \"$@\"", "--"]
14 changes: 14 additions & 0 deletions issue_credential/integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Integration Tests

All plugins should have a suite of integration tests. We use `docker compose` to set up the environment, and make use of the [Dockerfile](../docker/Dockerfile) to produce our ACA-Py/Plugin image. To simplify, we have another [Dockerfile](Dockerfile.test.runner) for running those [tests](/tests/).

## Build and run Tests

The integration tests will start 2 agents - bob and alice - and a juggernaut container that will execute the tests. Test results will be found in the juggernaut container output. The juggernaut container should close itself down, the logs can be reviewed in the `Docker` view, open `Containers`, open `integration`, right-click the `integration-tests` container and select `View Logs`

```sh
# open a terminal in vs code
cd integration
docker compose build
docker compose up
```
33 changes: 33 additions & 0 deletions issue_credential/integration/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: '3'
#***************************************************************
# integration level test agents *
#***************************************************************

services:
bob:
image: plugin-image
build:
context: ..
dockerfile: docker/Dockerfile
args:
- install_flags=--no-interaction --with integration --extras aca-py
command: start --arg-file integration.yml --label bob -e http://bob:3000 --log-level debug

alice:
image: plugin-image
command: start --arg-file integration.yml --label alice -e http://alice:3000 --log-level debug

tests:
container_name: juggernaut
build:
context: .
dockerfile: Dockerfile.test.runner
environment:
- WAIT_BEFORE_HOSTS=3
- WAIT_HOSTS=bob:3000, alice:3000
- WAIT_HOSTS_TIMEOUT=60
- WAIT_SLEEP_INTERVAL=1
- WAIT_HOST_CONNECT_TIMEOUT=30
depends_on:
- bob
- alice
19 changes: 19 additions & 0 deletions issue_credential/integration/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[tool.poetry]
name = "plugin-globals-tests"
version = "0.1.0"
description = ""
authors = []

[tool.poetry.dependencies]
python = "^3.12"
pytest = "^8.2.2"
pytest-asyncio = "~0.23.7"
requests = "^2.32.0"

[tool.poetry.dev-dependencies]

[tool.pytest.ini_options]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
108 changes: 108 additions & 0 deletions issue_credential/integration/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from functools import wraps

import pytest
import requests

AUTO_ACCEPT = "false"

BOB = "http://bob:3001"
ALICE = "http://alice:3001"


def get(agent: str, path: str, **kwargs):
"""Get."""
return requests.get(f"{agent}{path}", **kwargs)


def post(agent: str, path: str, **kwargs):
"""Post."""
return requests.post(f"{agent}{path}", **kwargs)


def fail_if_not_ok(message: str):
"""Fail the current test if wrapped call fails with message."""

def _fail_if_not_ok(func):
@wraps(func)
def _wrapper(*args, **kwargs):
response = func(*args, **kwargs)
if not response.ok:
pytest.fail(f"{message}: {response.content}")
return response

return _wrapper

return _fail_if_not_ok


def unwrap_json_response(func):
"""Unwrap a requests response object to json."""

@wraps(func)
def _wrapper(*args, **kwargs) -> dict:
response = func(*args, **kwargs)
return response.json()

return _wrapper


class Agent:
"""Class for interacting with Agent over Admin API"""

def __init__(self, url: str):
self.url = url

@unwrap_json_response
@fail_if_not_ok("Create invitation failed")
def create_invitation(self, **kwargs):
"""Create invitation."""
return post(self.url, "/connections/create-invitation", params=kwargs)

@unwrap_json_response
@fail_if_not_ok("Receive invitation failed")
def receive_invite(self, invite: dict, **kwargs):
"""Receive invitation."""
return post(
self.url, "/connections/receive-invitation", params=kwargs, json=invite
)

@unwrap_json_response
@fail_if_not_ok("Accept invitation failed")
def accept_invite(self, connection_id: str):
"""Accept invitation."""
return post(
self.url,
f"/connections/{connection_id}/accept-invitation",
)

@unwrap_json_response
@fail_if_not_ok("Failed to send basic message")
def send_message(self, connection_id, content):
"""Set connection metadata."""
return post(
self.url,
f"/connections/{connection_id}/send-message",
json={"content": content},
)

def get(self, path: str, return_json: bool = True, fail_with: str = None, **kwargs):
"""Do get to agent endpoint."""
wrapped_get = get
if fail_with:
wrapped_get = fail_if_not_ok(fail_with)(wrapped_get)
if return_json:
wrapped_get = unwrap_json_response(wrapped_get)

return wrapped_get(self.url, path, **kwargs)

def post(
self, path: str, return_json: bool = True, fail_with: str = None, **kwargs
):
"""Do get to agent endpoint."""
wrapped_post = post
if fail_with:
wrapped_post = fail_if_not_ok(fail_with)(wrapped_post)
if return_json:
wrapped_post = unwrap_json_response(wrapped_post)

return wrapped_post(self.url, path, **kwargs)
Loading
Loading