From 6e2d31e1e8d762388bb8e71ffca51abf06d75e4f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 13:18:31 +0100 Subject: [PATCH 01/26] Prepared file structure --- go/.golangci.yml | 43 ++++++++++ go/bridge/compileBridges.sh | 17 ++++ go/bridge/helpers.go | 1 + go/bridge/main.go | 1 + go/bridge/responses.go | 31 ++++++++ go/go.mod | 3 + go/internal/client-interface.go | 1 + go/internal/fake-client.go | 3 + go/internal/real-client.go | 3 + python/.python-version | 1 + python/MANIFEST.in | 1 + python/README.md | 0 python/pyproject.toml | 25 ++++++ python/requirements.txt | 0 python/teams_lib_pzsp2_z1/__init__.py | 0 python/teams_lib_pzsp2_z1/client.py | 0 python/teams_lib_pzsp2_z1/config.py | 0 python/tests/test_client.py | 0 python/uv.lock | 109 ++++++++++++++++++++++++++ scripts/commit-msg.config.json | 20 +++++ scripts/setup-commit-msg-hook | 78 ++++++++++++++++++ 21 files changed, 337 insertions(+) create mode 100644 go/.golangci.yml create mode 100644 go/bridge/compileBridges.sh create mode 100644 go/bridge/helpers.go create mode 100644 go/bridge/main.go create mode 100644 go/bridge/responses.go create mode 100644 go/go.mod create mode 100644 go/internal/client-interface.go create mode 100644 go/internal/fake-client.go create mode 100644 go/internal/real-client.go create mode 100644 python/.python-version create mode 100644 python/MANIFEST.in create mode 100644 python/README.md create mode 100644 python/pyproject.toml create mode 100644 python/requirements.txt create mode 100644 python/teams_lib_pzsp2_z1/__init__.py create mode 100644 python/teams_lib_pzsp2_z1/client.py create mode 100644 python/teams_lib_pzsp2_z1/config.py create mode 100644 python/tests/test_client.py create mode 100644 python/uv.lock create mode 100644 scripts/commit-msg.config.json create mode 100644 scripts/setup-commit-msg-hook diff --git a/go/.golangci.yml b/go/.golangci.yml new file mode 100644 index 0000000..268a2fe --- /dev/null +++ b/go/.golangci.yml @@ -0,0 +1,43 @@ +version: "2" +run: + tests: true +linters: + enable: + - gocritic + - gocyclo + - misspell + - revive + - unconvert + - unparam + - whitespace + settings: + gocritic: + enabled-tags: + - diagnostic + - performance + - style + gocyclo: + min-complexity: 15 + revive: + rules: + - name: exported + disabled: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ \ No newline at end of file diff --git a/go/bridge/compileBridges.sh b/go/bridge/compileBridges.sh new file mode 100644 index 0000000..ef5e2dc --- /dev/null +++ b/go/bridge/compileBridges.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e + +# Directory to save the compiled binaries +BIN_DIR="./python/teamsClientPZSP2/bin" +mkdir -p "$BIN_DIR" + +# Path to Go bridge +BRIDGE_PATH="./API-bridge/" + +echo "Building Linux..." +GOOS=linux GOARCH=amd64 go build -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PATH" + +echo "Building Windows..." +GOOS=windows GOARCH=amd64 go build -o "$BIN_DIR/teamsClientLib_windows.exe" "$BRIDGE_PATH" + +echo "Done! Binaries saved in $BIN_DIR" \ No newline at end of file diff --git a/go/bridge/helpers.go b/go/bridge/helpers.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/go/bridge/helpers.go @@ -0,0 +1 @@ +package main diff --git a/go/bridge/main.go b/go/bridge/main.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/go/bridge/main.go @@ -0,0 +1 @@ +package main diff --git a/go/bridge/responses.go b/go/bridge/responses.go new file mode 100644 index 0000000..dc384cb --- /dev/null +++ b/go/bridge/responses.go @@ -0,0 +1,31 @@ +package main + +import ( + "bufio" + "encoding/json" +) + +type response struct { + Result interface{} `json:"result,omitempty"` + Error string `json:"error,omitempty"` +} + +func respondError(writer *bufio.Writer, err error) { + response := response{ + Error: err.Error(), + } + respBytes, _ := json.Marshal(response) + writer.Write(respBytes) + writer.WriteString("\n") + writer.Flush() +} + +func respondResult(writer *bufio.Writer, result interface{}) { + response := response{ + Result: result, + } + respBytes, _ := json.Marshal(response) + writer.Write(respBytes) + writer.WriteString("\n") + writer.Flush() +} \ No newline at end of file diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..7738dc5 --- /dev/null +++ b/go/go.mod @@ -0,0 +1,3 @@ +module github.com/pzsp-teams/lib-python + +go 1.25.5 diff --git a/go/internal/client-interface.go b/go/internal/client-interface.go new file mode 100644 index 0000000..5bf0569 --- /dev/null +++ b/go/internal/client-interface.go @@ -0,0 +1 @@ +package internal diff --git a/go/internal/fake-client.go b/go/internal/fake-client.go new file mode 100644 index 0000000..69253d4 --- /dev/null +++ b/go/internal/fake-client.go @@ -0,0 +1,3 @@ +//go:build fake + +package internal diff --git a/go/internal/real-client.go b/go/internal/real-client.go new file mode 100644 index 0000000..771a186 --- /dev/null +++ b/go/internal/real-client.go @@ -0,0 +1,3 @@ +//go:build real + +package internal diff --git a/python/.python-version b/python/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/python/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/python/MANIFEST.in b/python/MANIFEST.in new file mode 100644 index 0000000..cd81339 --- /dev/null +++ b/python/MANIFEST.in @@ -0,0 +1 @@ +include teams_lib_pzsp2_z1/bin/* \ No newline at end of file diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..e69de29 diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..c7571bd --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "teams_lib_pzsp2_z1" +version = "0.1.0" +description = "Bridge to Go client for Teams API" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [] + +[dependency-groups] +dev = [ + "pytest>=9.0.0", + "ruff>=0.14.4", +] + +[tool.ruff] +line-length = 88 +indent-width = 4 +target-version = "py312" +fix = true +show-fixes = true +src = ["teams_lib_pzsp2_z1"] + + +[tool.ruff.lint] +extend-select = ["I", "E", "W", "F", "C90", "B", "S", "UP", "PL"] diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/python/teams_lib_pzsp2_z1/__init__.py b/python/teams_lib_pzsp2_z1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py new file mode 100644 index 0000000..e69de29 diff --git a/python/teams_lib_pzsp2_z1/config.py b/python/teams_lib_pzsp2_z1/config.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tests/test_client.py b/python/tests/test_client.py new file mode 100644 index 0000000..e69de29 diff --git a/python/uv.lock b/python/uv.lock new file mode 100644 index 0000000..eea846e --- /dev/null +++ b/python/uv.lock @@ -0,0 +1,109 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" }, + { url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" }, + { url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" }, + { url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" }, + { url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" }, + { url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" }, + { url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" }, + { url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" }, + { url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" }, + { url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" }, + { url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" }, + { url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" }, +] + +[[package]] +name = "teams-lib-pzsp2-z1" +version = "0.1.0" +source = { virtual = "." } + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0.0" }, + { name = "ruff", specifier = ">=0.14.4" }, +] diff --git a/scripts/commit-msg.config.json b/scripts/commit-msg.config.json new file mode 100644 index 0000000..b22c860 --- /dev/null +++ b/scripts/commit-msg.config.json @@ -0,0 +1,20 @@ +{ + "enabled": true, + "revert": true, + "length": { + "min": 1, + "max": 52 + }, + "types": [ + "build", + "ci", + "docs", + "feat", + "fix", + "perf", + "refactor", + "style", + "test", + "chore" + ] +} diff --git a/scripts/setup-commit-msg-hook b/scripts/setup-commit-msg-hook new file mode 100644 index 0000000..69642fb --- /dev/null +++ b/scripts/setup-commit-msg-hook @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +set -e + +HOOK_FILE=".git/hooks/commit-msg" +CONFIG_FILE="./scripts/commit-msg.config.json" + +if ! command -v jq >/dev/null 2>&1; then + echo "jq not found. Installing..." + + if command -v apt >/dev/null 2>&1; then + sudo apt update && sudo apt install -y jq + elif command -v dnf >/dev/null 2>&1; then + sudo dnf install -y jq + elif command -v brew >/dev/null 2>&1; then + brew install jq + else + echo "No supported package manager detected. Install jq manually." + exit 1 + fi +else + echo "jq already installed." +fi + +if [[ ! -f "$CONFIG_FILE" ]]; then + echo "Missing $CONFIG_FILE – create it before using this hook." + exit 1 +fi + +mkdir -p .git/hooks + +cat >"$HOOK_FILE" <<'EOF' +#!/usr/bin/env bash + +config=./scripts/commit-msg.config.json + +# set variables +enabled=$(jq -r .enabled $config) +revert=$(jq -r .revert $config) +types=($(jq -r '.types[]' $config)) +min_length=$(jq -r .length.min $config) +max_length=$(jq -r .length.max $config) + +if [[ ! -f $config || ! $enabled ]]; then + exit 0 +fi + +regexp="^(" + +if $revert; then + regexp="${regexp}revert: )?(\w+)(" +fi + +for type in "${types[@]}" +do + regexp="${regexp}$type|" +done + +regexp="${regexp})(\(.+\))?: " + +regexp="${regexp}.{$min_length,$max_length}$" + +msg=$(head -1 "$1") + +if [[ ! $msg =~ $regexp ]]; then + echo -e "\n\e[1m\e[31m[INVALID COMMIT MESSAGE]" + echo -e "------------------------\033[0m\e[0m" + echo -e "\e[1mValid types:\e[0m \e[34m${types[@]}\033[0m" + echo -e "\e[1mMax length (first line):\e[0m \e[34m$max_length\033[0m" + echo -e "\e[1mMin length (first line):\e[0m \e[34m$min_length\033[0m\n" + + exit 1 +fi +EOF + +chmod +x "$HOOK_FILE" + +echo "commit-msg hook installed successfully." From 1142c887c88dbad39bfa718a7671fa34134eca52 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 16:04:12 +0100 Subject: [PATCH 02/26] feat: started clearing code for main json parser --- go/bridge/helpers.go | 43 ++++++++++++++++- go/bridge/json-model.go | 83 +++++++++++++++++++++++++++++++++ go/bridge/main.go | 83 +++++++++++++++++++++++++++++++++ go/bridge/responses.go | 9 ++++ go/go.mod | 33 +++++++++++++ go/go.sum | 78 +++++++++++++++++++++++++++++++ go/internal/client-interface.go | 2 +- go/internal/fake-client.go | 2 +- go/internal/real-client.go | 2 +- 9 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 go/bridge/json-model.go create mode 100644 go/go.sum diff --git a/go/bridge/helpers.go b/go/bridge/helpers.go index 06ab7d0..696cc3c 100644 --- a/go/bridge/helpers.go +++ b/go/bridge/helpers.go @@ -1 +1,42 @@ -package main +package main + +import ( + "fmt" +) + +func safeString(m map[string]interface{}, key string) string { + if v, ok := m[key].(string); ok { + return v + } + return "" +} + +func safeInt(m map[string]interface{}, key string) int { + if v, ok := m[key].(float64); ok { + return int(v) + } + return 0 +} + +func safeScopes(m map[string]interface{}) ([]string, error) { + raw, ok := m["scopes"].([]interface{}) + if !ok { + return nil, fmt.Errorf("invalid scopes format") + } + out := make([]string, 0, len(raw)) + for _, v := range raw { + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("invalid scope value") + } + out = append(out, s) + } + return out, nil +} + +func validateAuthMethod(method string) (string, error) { + if method == "DEVICE_CODE" || method == "INTERACTIVE" { + return method, nil + } + return "", fmt.Errorf("invalid auth method: %s", method) +} \ No newline at end of file diff --git a/go/bridge/json-model.go b/go/bridge/json-model.go new file mode 100644 index 0000000..a613c1c --- /dev/null +++ b/go/bridge/json-model.go @@ -0,0 +1,83 @@ +package main + +import ( + "fmt" + + "github.com/pzsp-teams/lib" +) + +type Request struct { + Type string `json:"type"` + Method string `json:"method,omitempty"` + Config Config `json:"config,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +type Config struct { + SenderConfigMap SenderConfigMap `json:"senderConfig"` + AuthConfigMap AuthConfigMap `json:"authConfig"` +} + +type SenderConfigMap struct { + MaxRetries int `json:"maxRetries"` + NextRetryDelay int `json:"nextRetryDelay"` + Timeout int `json:"timeout"` +} + +func (scm SenderConfigMap) ToSenderConfig() (lib.SenderConfig, error) { + if scm.MaxRetries < 0 { + return lib.SenderConfig{}, fmt.Errorf("maxRetries cannot be negative") + } + if scm.NextRetryDelay < 0 { + return lib.SenderConfig{}, fmt.Errorf("nextRetryDelay cannot be negative") + } + if scm.Timeout <= 0 { + return lib.SenderConfig{}, fmt.Errorf("timeout must be > 0") + } + + return lib.SenderConfig{ + MaxRetries: scm.MaxRetries, + NextRetryDelay: scm.NextRetryDelay, + Timeout: scm.Timeout, + }, nil +} + +type AuthConfigMap struct { + ClientID string `json:"clientId"` + Tenant string `json:"tenant"` + Email string `json:"email"` + Scopes []string `json:"scopes"` + AuthMethod string `json:"authMethod"` +} + +func (acm AuthConfigMap) ToAuthConfig() (lib.AuthConfig, error) { + if acm.ClientID == "" { + return lib.AuthConfig{}, fmt.Errorf("clientId is required") + } + if acm.Tenant == "" { + return lib.AuthConfig{}, fmt.Errorf("tenant is required") + } + if acm.Email == "" { + return lib.AuthConfig{}, fmt.Errorf("email is required") + } + if len(acm.Scopes) == 0 { + return lib.AuthConfig{}, fmt.Errorf("scopes cannot be empty") + } + if acm.AuthMethod == "" { + return lib.AuthConfig{}, fmt.Errorf("authMethod is required") + } + + authMethod, err := validateAuthMethod(acm.AuthMethod) + if err != nil { + return lib.AuthConfig{}, err + } + + return lib.AuthConfig{ + ClientID: acm.ClientID, + Tenant: acm.Tenant, + Email: acm.Email, + Scopes: acm.Scopes, + AuthMethod: authMethod, + }, nil +} + diff --git a/go/bridge/main.go b/go/bridge/main.go index 06ab7d0..b51ffbe 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -1 +1,84 @@ package main + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "os" + + "github.com/pzsp-teams/lib" +) + +var client *lib.Client +var initialized bool + +func main() { + scanner := bufio.NewScanner(os.Stdin) + scanner.Buffer(make([]byte, 0, 1024), 1024*1024) + writer := bufio.NewWriter(os.Stdout) + + for scanner.Scan() { + line := scanner.Text() + + var req Request + err := json.Unmarshal([]byte(line), &req) + if err != nil { + respondError(writer, fmt.Errorf("invalid json: %w", err)) + continue + } + + if req.Type == "init" { + if initialized { + respondError(writer, fmt.Errorf("client already initialized")) + continue + } + + authConfig, err := req.Config.AuthConfigMap.ToAuthConfig() + if detectFail(writer, err) { + continue + } + + senderConfig, err := req.Config.SenderConfigMap.ToSenderConfig() + if detectFail(writer, err) { + continue + } + + c, err := lib.NewClient(context.TODO(), &authConfig, &senderConfig) + if detectFail(writer, err) { + continue + } + + client = c + initialized = true + respondResult(writer, "initialized") + continue + } + + if req.Type == "request" { + if client == nil { + respondError(writer, fmt.Errorf("client not initialized")) + continue + } + + switch req.Method { + case "listChannels": + teamRef := safeString(req.Params, "teamRef") + if teamRef == "" { + respondError(writer, fmt.Errorf("invalid teamRef parameter")) + continue + } + channels, err := client.Channels.ListChannels(context.TODO(), teamRef) + if err != nil { + respondError(writer, err) + } else { + respondResult(writer, channels) + } + default: + respondError(writer, fmt.Errorf("unknown method")) + } + continue + } + respondError(writer, fmt.Errorf("unknown request type")) + } +} diff --git a/go/bridge/responses.go b/go/bridge/responses.go index dc384cb..ff2c4f2 100644 --- a/go/bridge/responses.go +++ b/go/bridge/responses.go @@ -10,6 +10,15 @@ type response struct { Error string `json:"error,omitempty"` } +func detectFail(writer *bufio.Writer, err error) bool { + if err != nil { + respondError(writer, err) + return true + } + return false +} + + func respondError(writer *bufio.Writer, err error) { response := response{ Error: err.Error(), diff --git a/go/go.mod b/go/go.mod index 7738dc5..a147eaa 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,3 +1,36 @@ module github.com/pzsp-teams/lib-python go 1.25.5 + +require github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/microsoft/kiota-abstractions-go v1.9.3 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.3.1 // indirect + github.com/microsoft/kiota-http-go v1.5.4 // indirect + github.com/microsoft/kiota-serialization-form-go v1.1.2 // indirect + github.com/microsoft/kiota-serialization-json-go v1.1.2 // indirect + github.com/microsoft/kiota-serialization-multipart-go v1.1.2 // indirect + github.com/microsoft/kiota-serialization-text-go v1.1.3 // indirect + github.com/microsoftgraph/msgraph-sdk-go v1.90.0 // indirect + github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect +) diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 0000000..0f0c6b9 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,78 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 h1:rG3VnJUnAWyiv7qYmmdOdSapzz6HM+zb9/uRFr0T5EM= +github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100/go.mod h1:qDHUvIjGZJUtdPtuP4WMu5/U4aVWbFw1MhlkJqCGmCQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/microsoft/kiota-abstractions-go v1.9.3 h1:cqhbqro+VynJ7kObmo7850h3WN2SbvoyhypPn8uJ1SE= +github.com/microsoft/kiota-abstractions-go v1.9.3/go.mod h1:f06pl3qSyvUHEfVNkiRpXPkafx7khZqQEb71hN/pmuU= +github.com/microsoft/kiota-authentication-azure-go v1.3.1 h1:AGta92S6IL1E6ZMDb8YYB7NVNTIFUakbtLKUdY5RTuw= +github.com/microsoft/kiota-authentication-azure-go v1.3.1/go.mod h1:26zylt2/KfKwEWZSnwHaMxaArpbyN/CuzkbotdYXF0g= +github.com/microsoft/kiota-http-go v1.5.4 h1:wSUmL1J+bTQlAWHjbRkSwr+SPAkMVYeYxxB85Zw0KFs= +github.com/microsoft/kiota-http-go v1.5.4/go.mod h1:L+5Ri+SzwELnUcNA0cpbFKp/pBbvypLh3Cd1PR6sjx0= +github.com/microsoft/kiota-serialization-form-go v1.1.2 h1:SD6MATqNw+Dc5beILlsb/D87C36HKC/Zw7l+N9+HY2A= +github.com/microsoft/kiota-serialization-form-go v1.1.2/go.mod h1:m4tY2JT42jAZmgbqFwPy3zGDF+NPJACuyzmjNXeuHio= +github.com/microsoft/kiota-serialization-json-go v1.1.2 h1:eJrPWeQ665nbjO0gsHWJ0Bw6V/ZHHU1OfFPaYfRG39k= +github.com/microsoft/kiota-serialization-json-go v1.1.2/go.mod h1:deaGt7fjZarywyp7TOTiRsjfYiyWxwJJPQZytXwYQn8= +github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwbk7/ox1TehjlCUUT3r1f8cNlkvn4= +github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= +github.com/microsoft/kiota-serialization-text-go v1.1.3 h1:8z7Cebn0YAAr++xswVgfdxZjnAZ4GOB9O7XP4+r5r/M= +github.com/microsoft/kiota-serialization-text-go v1.1.3/go.mod h1:NDSvz4A3QalGMjNboKKQI9wR+8k+ih8UuagNmzIRgTQ= +github.com/microsoftgraph/msgraph-sdk-go v1.90.0 h1:ygVeWfGB8TMO4rTFxtrYueZmj1mLqtDKW5UZ4iJwczU= +github.com/microsoftgraph/msgraph-sdk-go v1.90.0/go.mod h1:UdZWxbZiFvjPug9DYayD90JNiHjXyNRA39lEpcy3Kms= +github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0 h1:0SrIoFl7TQnMRrsi5TFaeNe0q8KO5lRzRp4GSCCL2So= +github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0/go.mod h1:A1iXs+vjsRjzANxF6UeKv2ACExG7fqTwHHbwh1FL+EE= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d h1:hFdPrkbnUngxrGdMkPVZpvY76T6QgnXo87u9wzXgNWY= +github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d/go.mod h1:uGrgoO+KmBc55Le/AeysA0d24HOCQHO55Uw5Z7naQo4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go/internal/client-interface.go b/go/internal/client-interface.go index 5bf0569..ff60116 100644 --- a/go/internal/client-interface.go +++ b/go/internal/client-interface.go @@ -1 +1 @@ -package internal +package clientinterface diff --git a/go/internal/fake-client.go b/go/internal/fake-client.go index 69253d4..e41cbfc 100644 --- a/go/internal/fake-client.go +++ b/go/internal/fake-client.go @@ -1,3 +1,3 @@ //go:build fake -package internal +package clientinterface diff --git a/go/internal/real-client.go b/go/internal/real-client.go index 771a186..aee6408 100644 --- a/go/internal/real-client.go +++ b/go/internal/real-client.go @@ -1,3 +1,3 @@ //go:build real -package internal +package clientinterface From d9f52062e2657166add3755edfcbd3b1e488e1e6 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 16:33:34 +0100 Subject: [PATCH 03/26] feat: wrapped real client to an interface (to detect error on compilations stage) --- go/bridge/helpers.go | 7 ---- go/bridge/main.go | 21 ++++-------- go/internal/client-interface.go | 1 - go/internal/fake-client.go | 3 -- go/internal/json-client/client-interface.go | 34 +++++++++++++++++++ go/internal/json-client/fake-client.go | 3 ++ go/internal/json-client/real-client.go | 34 +++++++++++++++++++ .../json-model}/json-model.go | 9 ++++- go/internal/real-client.go | 3 -- 9 files changed, 85 insertions(+), 30 deletions(-) delete mode 100644 go/internal/client-interface.go delete mode 100644 go/internal/fake-client.go create mode 100644 go/internal/json-client/client-interface.go create mode 100644 go/internal/json-client/fake-client.go create mode 100644 go/internal/json-client/real-client.go rename go/{bridge => internal/json-model}/json-model.go (91%) delete mode 100644 go/internal/real-client.go diff --git a/go/bridge/helpers.go b/go/bridge/helpers.go index 696cc3c..dda5351 100644 --- a/go/bridge/helpers.go +++ b/go/bridge/helpers.go @@ -33,10 +33,3 @@ func safeScopes(m map[string]interface{}) ([]string, error) { } return out, nil } - -func validateAuthMethod(method string) (string, error) { - if method == "DEVICE_CODE" || method == "INTERACTIVE" { - return method, nil - } - return "", fmt.Errorf("invalid auth method: %s", method) -} \ No newline at end of file diff --git a/go/bridge/main.go b/go/bridge/main.go index b51ffbe..a65aeed 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -7,10 +7,11 @@ import ( "fmt" "os" - "github.com/pzsp-teams/lib" + jsonClient "github.com/pzsp-teams/lib-python/internal/json-client" + jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) -var client *lib.Client +var client jsonClient.TeamsJSONClient var initialized bool func main() { @@ -21,7 +22,7 @@ func main() { for scanner.Scan() { line := scanner.Text() - var req Request + var req jsonModel.Request err := json.Unmarshal([]byte(line), &req) if err != nil { respondError(writer, fmt.Errorf("invalid json: %w", err)) @@ -34,17 +35,7 @@ func main() { continue } - authConfig, err := req.Config.AuthConfigMap.ToAuthConfig() - if detectFail(writer, err) { - continue - } - - senderConfig, err := req.Config.SenderConfigMap.ToSenderConfig() - if detectFail(writer, err) { - continue - } - - c, err := lib.NewClient(context.TODO(), &authConfig, &senderConfig) + c, err := jsonClient.NewRealClient(req) if detectFail(writer, err) { continue } @@ -68,7 +59,7 @@ func main() { respondError(writer, fmt.Errorf("invalid teamRef parameter")) continue } - channels, err := client.Channels.ListChannels(context.TODO(), teamRef) + channels, err := client.Channels().ListChannels(context.TODO(), teamRef) if err != nil { respondError(writer, err) } else { diff --git a/go/internal/client-interface.go b/go/internal/client-interface.go deleted file mode 100644 index ff60116..0000000 --- a/go/internal/client-interface.go +++ /dev/null @@ -1 +0,0 @@ -package clientinterface diff --git a/go/internal/fake-client.go b/go/internal/fake-client.go deleted file mode 100644 index e41cbfc..0000000 --- a/go/internal/fake-client.go +++ /dev/null @@ -1,3 +0,0 @@ -//go:build fake - -package clientinterface diff --git a/go/internal/json-client/client-interface.go b/go/internal/json-client/client-interface.go new file mode 100644 index 0000000..3b032a0 --- /dev/null +++ b/go/internal/json-client/client-interface.go @@ -0,0 +1,34 @@ +package jsonclient + +import ( + "context" + + lib "github.com/pzsp-teams/lib" + models "github.com/pzsp-teams/lib/models" +) + +type TeamsJSONClient interface { + Teams() teamsService + Channels() channelsService +} + +type teamsService interface { + ListMyJoined(ctx context.Context) ([]*models.Team, error) +} + +type channelsService interface { + ListChannels(ctx context.Context, team string) ([]*models.Channel, error) + ListMessages(ctx context.Context, team, channel string, opts *models.ListMessagesOptions) ([]*models.Message, error) +} + +type clientAdapter struct { + client *lib.Client +} + +func (c *clientAdapter) Teams() teamsService { + return c.client.Teams +} + +func (c *clientAdapter) Channels() channelsService { + return c.client.Channels +} diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go new file mode 100644 index 0000000..4ee7274 --- /dev/null +++ b/go/internal/json-client/fake-client.go @@ -0,0 +1,3 @@ +//go:build fake + +package jsonclient diff --git a/go/internal/json-client/real-client.go b/go/internal/json-client/real-client.go new file mode 100644 index 0000000..58d4cab --- /dev/null +++ b/go/internal/json-client/real-client.go @@ -0,0 +1,34 @@ +//go:build real + +package jsonclient + +import ( + "context" + + lib "github.com/pzsp-teams/lib" + jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" +) + +func wrapRealClient(client *lib.Client) TeamsJSONClient { + return &clientAdapter{client} +} + +func NewRealClient(req jsonModel.Request) (TeamsJSONClient, error) { + authConfig, err := req.Config.AuthConfigMap.ToAuthConfig() + if err != nil { + return nil, err + } + + senderConfig, err := req.Config.SenderConfigMap.ToSenderConfig() + if err != nil { + return nil, err + } + + c, err := lib.NewClient(context.TODO(), &authConfig, &senderConfig) + if err != nil { + return nil, err + } + + return wrapRealClient(c), nil +} + diff --git a/go/bridge/json-model.go b/go/internal/json-model/json-model.go similarity index 91% rename from go/bridge/json-model.go rename to go/internal/json-model/json-model.go index a613c1c..a2d4c5d 100644 --- a/go/bridge/json-model.go +++ b/go/internal/json-model/json-model.go @@ -1,4 +1,4 @@ -package main +package jsonmodel import ( "fmt" @@ -81,3 +81,10 @@ func (acm AuthConfigMap) ToAuthConfig() (lib.AuthConfig, error) { }, nil } +func validateAuthMethod(method string) (string, error) { + if method == "DEVICE_CODE" || method == "INTERACTIVE" { + return method, nil + } + return "", fmt.Errorf("invalid auth method: %s", method) +} + diff --git a/go/internal/real-client.go b/go/internal/real-client.go deleted file mode 100644 index aee6408..0000000 --- a/go/internal/real-client.go +++ /dev/null @@ -1,3 +0,0 @@ -//go:build real - -package clientinterface From 6c64f3132a71059f04fe68750f957e071746cacc Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 17:01:05 +0100 Subject: [PATCH 04/26] refactor: moved requests to TeamsJSONClient --- go/bridge/main.go | 12 ++----- go/internal/json-client/client-interface.go | 34 -------------------- go/internal/json-client/real-client.go | 10 ++---- go/internal/json-client/requests-channels.go | 23 +++++++++++++ go/internal/json-client/teams-json-client.go | 33 +++++++++++++++++++ 5 files changed, 62 insertions(+), 50 deletions(-) delete mode 100644 go/internal/json-client/client-interface.go create mode 100644 go/internal/json-client/requests-channels.go create mode 100644 go/internal/json-client/teams-json-client.go diff --git a/go/bridge/main.go b/go/bridge/main.go index a65aeed..de4dafa 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "context" "encoding/json" "fmt" "os" @@ -11,7 +10,7 @@ import ( jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) -var client jsonClient.TeamsJSONClient +var client *jsonClient.TeamsJSONClient var initialized bool func main() { @@ -35,7 +34,7 @@ func main() { continue } - c, err := jsonClient.NewRealClient(req) + c, err := jsonClient.NewRealJSONClient(req) if detectFail(writer, err) { continue } @@ -54,12 +53,7 @@ func main() { switch req.Method { case "listChannels": - teamRef := safeString(req.Params, "teamRef") - if teamRef == "" { - respondError(writer, fmt.Errorf("invalid teamRef parameter")) - continue - } - channels, err := client.Channels().ListChannels(context.TODO(), teamRef) + channels, err := client.ListChannels(req.Params) if err != nil { respondError(writer, err) } else { diff --git a/go/internal/json-client/client-interface.go b/go/internal/json-client/client-interface.go deleted file mode 100644 index 3b032a0..0000000 --- a/go/internal/json-client/client-interface.go +++ /dev/null @@ -1,34 +0,0 @@ -package jsonclient - -import ( - "context" - - lib "github.com/pzsp-teams/lib" - models "github.com/pzsp-teams/lib/models" -) - -type TeamsJSONClient interface { - Teams() teamsService - Channels() channelsService -} - -type teamsService interface { - ListMyJoined(ctx context.Context) ([]*models.Team, error) -} - -type channelsService interface { - ListChannels(ctx context.Context, team string) ([]*models.Channel, error) - ListMessages(ctx context.Context, team, channel string, opts *models.ListMessagesOptions) ([]*models.Message, error) -} - -type clientAdapter struct { - client *lib.Client -} - -func (c *clientAdapter) Teams() teamsService { - return c.client.Teams -} - -func (c *clientAdapter) Channels() channelsService { - return c.client.Channels -} diff --git a/go/internal/json-client/real-client.go b/go/internal/json-client/real-client.go index 58d4cab..82b31d0 100644 --- a/go/internal/json-client/real-client.go +++ b/go/internal/json-client/real-client.go @@ -9,11 +9,7 @@ import ( jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) -func wrapRealClient(client *lib.Client) TeamsJSONClient { - return &clientAdapter{client} -} - -func NewRealClient(req jsonModel.Request) (TeamsJSONClient, error) { +func NewRealJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { authConfig, err := req.Config.AuthConfigMap.ToAuthConfig() if err != nil { return nil, err @@ -24,11 +20,11 @@ func NewRealClient(req jsonModel.Request) (TeamsJSONClient, error) { return nil, err } - c, err := lib.NewClient(context.TODO(), &authConfig, &senderConfig) + client, err := lib.NewClient(context.TODO(), &authConfig, &senderConfig) if err != nil { return nil, err } - return wrapRealClient(c), nil + return &TeamsJSONClient{client}, nil } diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go new file mode 100644 index 0000000..967a67e --- /dev/null +++ b/go/internal/json-client/requests-channels.go @@ -0,0 +1,23 @@ +package jsonclient + +import ( + "context" + "fmt" +) + +type ListChannelsParams struct { + TeamRef string +} + +func (jsonclient *TeamsJSONClient) ListChannels(params map[string]interface{}) (interface{}, error) { + teamRef, ok := params["teamRef"].(string) + if !ok || teamRef == "" { + return nil, fmt.Errorf("invalid teamRef parameter") + } + channels, err := jsonclient.client.Channels.ListChannels(context.TODO(), teamRef) + if err != nil { + return nil, err + } else { + return channels, nil + } +} diff --git a/go/internal/json-client/teams-json-client.go b/go/internal/json-client/teams-json-client.go new file mode 100644 index 0000000..237356a --- /dev/null +++ b/go/internal/json-client/teams-json-client.go @@ -0,0 +1,33 @@ +package jsonclient + +import ( + // "context" + + lib "github.com/pzsp-teams/lib" + // models "github.com/pzsp-teams/lib/models" +) + +type TeamsJSONClient struct { + client *lib.Client +} + +// type teamsService interface { +// ListMyJoined(ctx context.Context) ([]*models.Team, error) +// } + +// type channelsService interface { +// ListChannels(ctx context.Context, team string) ([]*models.Channel, error) +// ListMessages(ctx context.Context, team, channel string, opts *models.ListMessagesOptions) ([]*models.Message, error) +// } + +// type clientAdapter struct { +// client *lib.Client +// } + +// func (c *clientAdapter) Teams() teamsService { +// return c.client.Teams +// } + +// func (c *clientAdapter) Channels() channelsService { +// return c.client.Channels +// } From 654ae95c141216cc5de4a363c927844746a0170a Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 19:50:05 +0100 Subject: [PATCH 05/26] feat: implemented mocking fake client --- go/bridge/helpers.go | 35 ----------- go/go.mod | 8 ++- go/internal/json-client/fake-client.go | 83 ++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 38 deletions(-) delete mode 100644 go/bridge/helpers.go diff --git a/go/bridge/helpers.go b/go/bridge/helpers.go deleted file mode 100644 index dda5351..0000000 --- a/go/bridge/helpers.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "fmt" -) - -func safeString(m map[string]interface{}, key string) string { - if v, ok := m[key].(string); ok { - return v - } - return "" -} - -func safeInt(m map[string]interface{}, key string) int { - if v, ok := m[key].(float64); ok { - return int(v) - } - return 0 -} - -func safeScopes(m map[string]interface{}) ([]string, error) { - raw, ok := m["scopes"].([]interface{}) - if !ok { - return nil, fmt.Errorf("invalid scopes format") - } - out := make([]string, 0, len(raw)) - for _, v := range raw { - s, ok := v.(string) - if !ok { - return nil, fmt.Errorf("invalid scope value") - } - out = append(out, s) - } - return out, nil -} diff --git a/go/go.mod b/go/go.mod index a147eaa..89d73c1 100644 --- a/go/go.mod +++ b/go/go.mod @@ -2,7 +2,11 @@ module github.com/pzsp-teams/lib-python go 1.25.5 -require github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d +require ( + github.com/microsoft/kiota-abstractions-go v1.9.3 + github.com/microsoftgraph/msgraph-sdk-go v1.90.0 + github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d +) require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect @@ -15,14 +19,12 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/microsoft/kiota-abstractions-go v1.9.3 // indirect github.com/microsoft/kiota-authentication-azure-go v1.3.1 // indirect github.com/microsoft/kiota-http-go v1.5.4 // indirect github.com/microsoft/kiota-serialization-form-go v1.1.2 // indirect github.com/microsoft/kiota-serialization-json-go v1.1.2 // indirect github.com/microsoft/kiota-serialization-multipart-go v1.1.2 // indirect github.com/microsoft/kiota-serialization-text-go v1.1.3 // indirect - github.com/microsoftgraph/msgraph-sdk-go v1.90.0 // indirect github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go index 4ee7274..682c05f 100644 --- a/go/internal/json-client/fake-client.go +++ b/go/internal/json-client/fake-client.go @@ -1,3 +1,86 @@ //go:build fake package jsonclient + +import ( + "context" + "crypto/tls" + "net/http" + "strings" + + azcore "github.com/microsoft/kiota-abstractions-go" + graph "github.com/microsoftgraph/msgraph-sdk-go" + + lib "github.com/pzsp-teams/lib" +) + +// --- 1. Fake Auth Provider --- +type FakeAuthProvider struct{} + +func (f *FakeAuthProvider) AuthenticateRequest(ctx context.Context, request *azcore.RequestInformation, additionalAuthenticationContext map[string]interface{}) error { + if request.Headers == nil { + request.Headers = azcore.NewRequestHeaders() + } + request.Headers.Add("Authorization", "Bearer fake-jwt-token") + + return nil +} + +// --- 2. Replace MS API with Python Mock Server --- +type HijackTransport struct { + MockServerURL string + Transport http.RoundTripper +} + +func (t *HijackTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.URL.Scheme = "http" + + targetHost := strings.TrimPrefix(t.MockServerURL, "http://") + targetHost = strings.TrimPrefix(targetHost, "https://") + req.URL.Host = targetHost + + return t.Transport.RoundTrip(req) +} + +// --- 3. Fake JSON Client Factory --- +func NewFakeJSONClient(mockUrl string) (*TeamsJSONClient, error) { + + // A. Konfiguracja SenderConfig (tak jak w wersji Real) + senderConfig := lib.SenderConfig{ + MaxRetries: 3, + NextRetryDelay: 2, + Timeout: 5, + } + + cacheEnabled := false + var cachePath *string = nil + + hijackedHttpClient := &http.Client{ + Transport: &HijackTransport{ + MockServerURL: mockUrl, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + }, + } + authProvider := &FakeAuthProvider{} + + adapter, err := graph.NewGraphRequestAdapterWithParseNodeFactoryAndSerializationWriterFactoryAndHttpClient( + authProvider, + nil, // default ParseNodeFactory + nil, // default SerializationWriterFactory + hijackedHttpClient, + ) + if err != nil { + return nil, err + } + + graphClient := graph.NewGraphServiceClient(adapter) + + client, err := lib.NewClientFromGraphClient(graphClient, &senderConfig, cacheEnabled, cachePath) + if err != nil { + return nil, err + } + + return &TeamsJSONClient{client}, nil +} From f172ed885c0b43e309471c9b727a1cc79b3aa5ad Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 19:58:11 +0100 Subject: [PATCH 06/26] feat: implemented creating fake-client through stdin --- go/bridge/main.go | 17 +++++++++++++++++ go/internal/json-client/fake-client.go | 11 +++++++++-- go/internal/json-client/requests-channels.go | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/go/bridge/main.go b/go/bridge/main.go index de4dafa..1bda04c 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -45,6 +45,23 @@ func main() { continue } + if req.Type == "init-fake" { + if initialized { + respondError(writer, fmt.Errorf("client already initialized")) + continue + } + + c, err := jsonClient.NewFakeJSONClient(req) + if detectFail(writer, err) { + continue + } + + client = c + initialized = true + respondResult(writer, "initialized") + continue + } + if req.Type == "request" { if client == nil { respondError(writer, fmt.Errorf("client not initialized")) diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go index 682c05f..46e1fa9 100644 --- a/go/internal/json-client/fake-client.go +++ b/go/internal/json-client/fake-client.go @@ -4,6 +4,7 @@ package jsonclient import ( "context" + "fmt" "crypto/tls" "net/http" "strings" @@ -12,6 +13,7 @@ import ( graph "github.com/microsoftgraph/msgraph-sdk-go" lib "github.com/pzsp-teams/lib" + jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) // --- 1. Fake Auth Provider --- @@ -43,7 +45,12 @@ func (t *HijackTransport) RoundTrip(req *http.Request) (*http.Response, error) { } // --- 3. Fake JSON Client Factory --- -func NewFakeJSONClient(mockUrl string) (*TeamsJSONClient, error) { +func NewFakeJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { + // Parse parameters + mockServerURL, ok := req.Params["mockServerUrl"].(string) + if !ok || mockServerURL == "" { + return nil, fmt.Errorf("invalid mockServerUrl parameter") + } // A. Konfiguracja SenderConfig (tak jak w wersji Real) senderConfig := lib.SenderConfig{ @@ -57,7 +64,7 @@ func NewFakeJSONClient(mockUrl string) (*TeamsJSONClient, error) { hijackedHttpClient := &http.Client{ Transport: &HijackTransport{ - MockServerURL: mockUrl, + MockServerURL: mockServerURL, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, diff --git a/go/internal/json-client/requests-channels.go b/go/internal/json-client/requests-channels.go index 967a67e..cdfb18a 100644 --- a/go/internal/json-client/requests-channels.go +++ b/go/internal/json-client/requests-channels.go @@ -6,7 +6,7 @@ import ( ) type ListChannelsParams struct { - TeamRef string + TeamRef string `json:"teamRef"` } func (jsonclient *TeamsJSONClient) ListChannels(params map[string]interface{}) (interface{}, error) { From 6707904b9b9fc07afa4d65ce40361cc4399bcef5 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 20:27:36 +0100 Subject: [PATCH 07/26] feat: implemented python client structure --- go/bridge/compileBridges.sh | 4 +- python/pyproject.toml | 4 +- python/teams_lib_pzsp2_z1/client.py | 105 ++++++++++++++++++ python/teams_lib_pzsp2_z1/config.py | 61 ++++++++++ .../services/base_service.py | 6 + .../teams_lib_pzsp2_z1/services/channels.py | 14 +++ python/uv.lock | 24 ++++ 7 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 python/teams_lib_pzsp2_z1/services/base_service.py create mode 100644 python/teams_lib_pzsp2_z1/services/channels.py diff --git a/go/bridge/compileBridges.sh b/go/bridge/compileBridges.sh index ef5e2dc..afb1418 100644 --- a/go/bridge/compileBridges.sh +++ b/go/bridge/compileBridges.sh @@ -2,11 +2,11 @@ set -e # Directory to save the compiled binaries -BIN_DIR="./python/teamsClientPZSP2/bin" +BIN_DIR="./python/teams_lib_pzsp2_z1/bin" mkdir -p "$BIN_DIR" # Path to Go bridge -BRIDGE_PATH="./API-bridge/" +BRIDGE_PATH="./go/bridge/" echo "Building Linux..." GOOS=linux GOARCH=amd64 go build -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PATH" diff --git a/python/pyproject.toml b/python/pyproject.toml index c7571bd..4590e5e 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -4,7 +4,9 @@ version = "0.1.0" description = "Bridge to Go client for Teams API" readme = "README.md" requires-python = ">=3.12" -dependencies = [] +dependencies = [ + "dotenv>=0.9.9", +] [dependency-groups] dev = [ diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index e69de29..3e5e30a 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -0,0 +1,105 @@ +import json +import pathlib +import platform +import subprocess +import threading +from typing import Any + +from python.teams_lib_pzsp2_z1.services.channels import ChannelsService + +import config + + +class TeamsClient: + def __init__(self): + self._lock = threading.Lock() + + self.proc = subprocess.Popen( # noqa: S603 + [str(self._binary())], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True, + bufsize=1, + ) + + self.channels = ChannelsService(self) + + self.init_client() + + def get_response(self) -> Any: + res = json.loads(self.proc.stdout.readline()) + + if "error" in res and res["error"]: + raise RuntimeError(res["error"]) + + return res["result"] + + def _binary(self): + base = pathlib.Path(__file__).parent / "bin" + osname = platform.system() + + if osname == "Windows": + return base / "teamsClientLib_windows.exe" + elif osname == "Linux": + return base / "teamsClientLib_linux" + else: + raise RuntimeError("Unsupported OS") + + def init_client(self) -> Any: + sender_config = config.SenderConfig() + auth_config = config.load_auth_config() + return self.execute( + cmd_type="init", + method=None, + params={ + "config": { + "senderConfig": { + "maxRetries": sender_config.max_retries, + "nextRetryDelay": sender_config.next_retry_delay, + "timeout": sender_config.timeout, + }, + "authConfig": { + "clientID": auth_config.client_id, + "tenant": auth_config.tenant, + "email": auth_config.email, + "scopes": auth_config.scopes, + "authMethod": auth_config.auth_method, + }, + }, + }, + ) + + def execute( + self, cmd_type: str, method: str | None, params: dict[str, Any] | None = None + ) -> Any: + payload = {"type": cmd_type} + if method: + payload["method"] = method + if params: + payload.update(params) + + json_payload = json.dumps(payload) + + # Critical section to avoid interleaving requests/responses + with self._lock: + try: + self.proc.stdin.write(json_payload + "\n") + self.proc.stdin.flush() + + raw_response = self.proc.stdout.readline() + except BrokenPipeError: + raise RuntimeError("Go process crashed or closed connection") # noqa: B904 + + if not raw_response: + raise RuntimeError("Go process returned empty response") + + res = json.loads(raw_response) + + if "error" in res and res["error"]: + raise RuntimeError(f"Go Error: {res['error']}") + + return res.get("result") + + def close(self): + self.proc.terminate() diff --git a/python/teams_lib_pzsp2_z1/config.py b/python/teams_lib_pzsp2_z1/config.py index e69de29..77440e9 100644 --- a/python/teams_lib_pzsp2_z1/config.py +++ b/python/teams_lib_pzsp2_z1/config.py @@ -0,0 +1,61 @@ +import os +import sys +from dataclasses import dataclass + +from dotenv import load_dotenv + + +@dataclass +class SenderConfig: + max_retries: int = 3 + next_retry_delay: int = 2 + timeout: int = 10 + + +@dataclass +class AuthConfig: + client_id: str + tenant: str + email: str + scopes: list[str] + auth_method: str + + +def load_auth_config() -> AuthConfig: + load_dotenv() + + cfg = AuthConfig( + client_id=get_env("CLIENT_ID", ""), + tenant=get_env("TENANT_ID", ""), + email=get_env("EMAIL", ""), + scopes=get_env( + "SCOPES", + "https://graph.microsoft.com/.default" + ).split(","), + auth_method=get_env("AUTH_METHOD", "DEVICE_CODE"), + ) + + validate(cfg) + return cfg + + +def get_env(key: str, fallback: str) -> str: + return os.getenv(key, fallback) + + +def validate(cfg: AuthConfig): + if not cfg.client_id: + print("Missing CLIENT ID", file=sys.stderr) + sys.exit(1) + + if not cfg.tenant: + print("Missing TENANT ID", file=sys.stderr) + sys.exit(1) + + if not cfg.email: + print("Missing EMAIL", file=sys.stderr) + sys.exit(1) + + if cfg.auth_method not in ("DEVICE_CODE", "INTERACTIVE"): + print("AUTH METHOD must be either DEVICE_CODE or INTERACTIVE", file=sys.stderr) + sys.exit(1) diff --git a/python/teams_lib_pzsp2_z1/services/base_service.py b/python/teams_lib_pzsp2_z1/services/base_service.py new file mode 100644 index 0000000..ce8948a --- /dev/null +++ b/python/teams_lib_pzsp2_z1/services/base_service.py @@ -0,0 +1,6 @@ +from python.teams_lib_pzsp2_z1.client import TeamsClient + + +class BaseService: + def __init__(self, client: 'TeamsClient'): + self.client = client diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py new file mode 100644 index 0000000..ffea6c4 --- /dev/null +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -0,0 +1,14 @@ +from typing import Any + +from python.teams_lib_pzsp2_z1.services.base_service import BaseService + + +class ChannelsService(BaseService): + def list_channels(self, teamRef: str) -> Any: + return self.client.execute( + cmd_type="request", + method="listChannels", + params={ + "teamRef": teamRef, + }, + ) diff --git a/python/uv.lock b/python/uv.lock index eea846e..3f79cf3 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -11,6 +11,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "dotenv" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -63,6 +74,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + [[package]] name = "ruff" version = "0.14.8" @@ -93,6 +113,9 @@ wheels = [ name = "teams-lib-pzsp2-z1" version = "0.1.0" source = { virtual = "." } +dependencies = [ + { name = "dotenv" }, +] [package.dev-dependencies] dev = [ @@ -101,6 +124,7 @@ dev = [ ] [package.metadata] +requires-dist = [{ name = "dotenv", specifier = ">=0.9.9" }] [package.metadata.requires-dev] dev = [ From b6c3be8651fb8a055ec976a10018786835fd461f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 20:39:41 +0100 Subject: [PATCH 08/26] feat: implemented creating fake go client via stdout --- .gitignore | 1 + go/bridge/main.go | 2 +- python/teams_lib_pzsp2_z1/client.py | 27 ++++++++++--------- .../services/base_service.py | 8 ++++-- .../teams_lib_pzsp2_z1/services/channels.py | 2 +- 5 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e99e36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc \ No newline at end of file diff --git a/go/bridge/main.go b/go/bridge/main.go index 1bda04c..21bad62 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -45,7 +45,7 @@ func main() { continue } - if req.Type == "init-fake" { + if req.Type == "initFake" { if initialized { respondError(writer, fmt.Errorf("client already initialized")) continue diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index 3e5e30a..b8867f9 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -5,9 +5,8 @@ import threading from typing import Any -from python.teams_lib_pzsp2_z1.services.channels import ChannelsService - -import config +from teams_lib_pzsp2_z1 import config +from teams_lib_pzsp2_z1.services.channels import ChannelsService class TeamsClient: @@ -27,14 +26,6 @@ def __init__(self): self.init_client() - def get_response(self) -> Any: - res = json.loads(self.proc.stdout.readline()) - - if "error" in res and res["error"]: - raise RuntimeError(res["error"]) - - return res["result"] - def _binary(self): base = pathlib.Path(__file__).parent / "bin" osname = platform.system() @@ -51,7 +42,6 @@ def init_client(self) -> Any: auth_config = config.load_auth_config() return self.execute( cmd_type="init", - method=None, params={ "config": { "senderConfig": { @@ -70,8 +60,19 @@ def init_client(self) -> Any: }, ) + def init_fake_client(self, mock_server_url: str) -> Any: + return self.execute( + cmd_type="initFake", + params={ + "mockServerUrl": mock_server_url, + }, + ) + def execute( - self, cmd_type: str, method: str | None, params: dict[str, Any] | None = None + self, + cmd_type: str, + method: str | None = None, + params: dict[str, Any] | None = None, ) -> Any: payload = {"type": cmd_type} if method: diff --git a/python/teams_lib_pzsp2_z1/services/base_service.py b/python/teams_lib_pzsp2_z1/services/base_service.py index ce8948a..073288d 100644 --- a/python/teams_lib_pzsp2_z1/services/base_service.py +++ b/python/teams_lib_pzsp2_z1/services/base_service.py @@ -1,6 +1,10 @@ -from python.teams_lib_pzsp2_z1.client import TeamsClient +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from teams_lib_pzsp2_z1.client import TeamsClient class BaseService: - def __init__(self, client: 'TeamsClient'): + def __init__(self, client: TeamsClient): self.client = client diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index ffea6c4..f467a67 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -1,6 +1,6 @@ from typing import Any -from python.teams_lib_pzsp2_z1.services.base_service import BaseService +from teams_lib_pzsp2_z1.services.base_service import BaseService class ChannelsService(BaseService): From 491cf98afc80180d5789b3ddc64934f0751c36ea Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 21:09:56 +0100 Subject: [PATCH 09/26] feat: further improvements --- go/bridge/main.go | 23 +---- go/internal/json-client/fake-client.go | 2 +- go/internal/json-client/real-client.go | 3 +- python/pyproject.toml | 2 + python/teams_lib_pzsp2_z1/client.py | 33 +++---- python/teams_lib_pzsp2_z1/model/channel.py | 8 ++ .../teams_lib_pzsp2_z1/services/channels.py | 16 +++- python/tests/test_channels.py | 40 ++++++++ python/tests/test_client.py | 0 python/uv.lock | 93 ++++++++++++++++++- 10 files changed, 176 insertions(+), 44 deletions(-) create mode 100644 python/teams_lib_pzsp2_z1/model/channel.py create mode 100644 python/tests/test_channels.py delete mode 100644 python/tests/test_client.py diff --git a/go/bridge/main.go b/go/bridge/main.go index 21bad62..ec5052b 100644 --- a/go/bridge/main.go +++ b/go/bridge/main.go @@ -6,11 +6,11 @@ import ( "fmt" "os" - jsonClient "github.com/pzsp-teams/lib-python/internal/json-client" + jsonClientLib "github.com/pzsp-teams/lib-python/internal/json-client" jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) -var client *jsonClient.TeamsJSONClient +var client *jsonClientLib.TeamsJSONClient var initialized bool func main() { @@ -34,24 +34,7 @@ func main() { continue } - c, err := jsonClient.NewRealJSONClient(req) - if detectFail(writer, err) { - continue - } - - client = c - initialized = true - respondResult(writer, "initialized") - continue - } - - if req.Type == "initFake" { - if initialized { - respondError(writer, fmt.Errorf("client already initialized")) - continue - } - - c, err := jsonClient.NewFakeJSONClient(req) + c, err := jsonClientLib.NewJSONClient(req) if detectFail(writer, err) { continue } diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go index 46e1fa9..16db332 100644 --- a/go/internal/json-client/fake-client.go +++ b/go/internal/json-client/fake-client.go @@ -45,7 +45,7 @@ func (t *HijackTransport) RoundTrip(req *http.Request) (*http.Response, error) { } // --- 3. Fake JSON Client Factory --- -func NewFakeJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { +func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { // Parse parameters mockServerURL, ok := req.Params["mockServerUrl"].(string) if !ok || mockServerURL == "" { diff --git a/go/internal/json-client/real-client.go b/go/internal/json-client/real-client.go index 82b31d0..f246d2f 100644 --- a/go/internal/json-client/real-client.go +++ b/go/internal/json-client/real-client.go @@ -9,7 +9,7 @@ import ( jsonModel "github.com/pzsp-teams/lib-python/internal/json-model" ) -func NewRealJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { +func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { authConfig, err := req.Config.AuthConfigMap.ToAuthConfig() if err != nil { return nil, err @@ -27,4 +27,3 @@ func NewRealJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { return &TeamsJSONClient{client}, nil } - diff --git a/python/pyproject.toml b/python/pyproject.toml index 4590e5e..6835cb2 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -6,6 +6,7 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ "dotenv>=0.9.9", + "pytest-httpserver>=1.1.3", ] [dependency-groups] @@ -21,6 +22,7 @@ target-version = "py312" fix = true show-fixes = true src = ["teams_lib_pzsp2_z1"] +exclude = ["test_*.py", "tests/*"] [tool.ruff.lint] diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index b8867f9..1c75188 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -42,27 +42,25 @@ def init_client(self) -> Any: auth_config = config.load_auth_config() return self.execute( cmd_type="init", - params={ - "config": { - "senderConfig": { - "maxRetries": sender_config.max_retries, - "nextRetryDelay": sender_config.next_retry_delay, - "timeout": sender_config.timeout, - }, - "authConfig": { - "clientID": auth_config.client_id, - "tenant": auth_config.tenant, - "email": auth_config.email, - "scopes": auth_config.scopes, - "authMethod": auth_config.auth_method, - }, + config={ + "senderConfig": { + "maxRetries": sender_config.max_retries, + "nextRetryDelay": sender_config.next_retry_delay, + "timeout": sender_config.timeout, + }, + "authConfig": { + "clientID": auth_config.client_id, + "tenant": auth_config.tenant, + "email": auth_config.email, + "scopes": auth_config.scopes, + "authMethod": auth_config.auth_method, }, }, ) def init_fake_client(self, mock_server_url: str) -> Any: return self.execute( - cmd_type="initFake", + cmd_type="init", params={ "mockServerUrl": mock_server_url, }, @@ -72,13 +70,16 @@ def execute( self, cmd_type: str, method: str | None = None, + config: dict[str, Any] | None = None, params: dict[str, Any] | None = None, ) -> Any: payload = {"type": cmd_type} if method: payload["method"] = method if params: - payload.update(params) + payload["params"] = params + if config: + payload["config"] = config json_payload = json.dumps(payload) diff --git a/python/teams_lib_pzsp2_z1/model/channel.py b/python/teams_lib_pzsp2_z1/model/channel.py new file mode 100644 index 0000000..ace8276 --- /dev/null +++ b/python/teams_lib_pzsp2_z1/model/channel.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + + +@dataclass +class Channel: + ID: str + Name: str + IsGeneral: bool diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index f467a67..adb51e5 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -1,14 +1,22 @@ -from typing import Any - +from teams_lib_pzsp2_z1.model.channel import Channel from teams_lib_pzsp2_z1.services.base_service import BaseService class ChannelsService(BaseService): - def list_channels(self, teamRef: str) -> Any: - return self.client.execute( + def list_channels(self, teamRef: str) -> list[Channel]: + response = self.client.execute( cmd_type="request", method="listChannels", params={ "teamRef": teamRef, }, ) + channels_data = response.get("channels", []) + return [ + Channel( + ID=channel["id"], + Name=channel["displayName"], + IsGeneral=(channel["displayName"].lower() == "general"), + ) + for channel in channels_data + ] diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py new file mode 100644 index 0000000..51dcf3b --- /dev/null +++ b/python/tests/test_channels.py @@ -0,0 +1,40 @@ +from teams_lib_pzsp2_z1.client import TeamsClient + + +def test_list_channels_integration(httpserver): + """ + Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server + """ + # Fake server config + fake_team_id = "team-123-abc" + ms_graph_response = { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('team-123-abc')/channels", + "value": [ + { + "id": "19:123123@thread.tacv2", + "displayName": "General", + "description": "General discussions", + }, + { + "id": "19:999999@thread.tacv2", + "displayName": "Development", + "description": "Coding stuff", + }, + ], + } + httpserver.expect_request( + f"/v1.0/teams/{fake_team_id}/channels", method="GET" + ).respond_with_json(ms_graph_response) + + # Init fake client + client = TeamsClient() + client.init_fake_client(httpserver.url_for("")) + + channels = client.channels.list_channels(fake_team_id) + + assert len(channels) == 2 + + first_channel = channels[0] + + assert first_channel["displayName"] == "General" + assert first_channel["id"] == "19:123123@thread.tacv2" diff --git a/python/tests/test_client.py b/python/tests/test_client.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/uv.lock b/python/uv.lock index 3f79cf3..bc82c5f 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -31,6 +31,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -74,6 +137,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] +[[package]] +name = "pytest-httpserver" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/d8/def15ba33bd696dd72dd4562a5287c0cba4d18a591eeb82e0b08ab385afc/pytest_httpserver-1.1.3.tar.gz", hash = "sha256:af819d6b533f84b4680b9416a5b3f67f1df3701f1da54924afd4d6e4ba5917ec", size = 68870, upload-time = "2025-04-10T08:17:15.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/d2/dfc2f25f3905921c2743c300a48d9494d29032f1389fc142e718d6978fb2/pytest_httpserver-1.1.3-py3-none-any.whl", hash = "sha256:5f84757810233e19e2bb5287f3826a71c97a3740abe3a363af9155c0f82fdbb9", size = 21000, upload-time = "2025-04-10T08:17:13.906Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.1" @@ -115,6 +190,7 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "dotenv" }, + { name = "pytest-httpserver" }, ] [package.dev-dependencies] @@ -124,10 +200,25 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "dotenv", specifier = ">=0.9.9" }] +requires-dist = [ + { name = "dotenv", specifier = ">=0.9.9" }, + { name = "pytest-httpserver", specifier = ">=1.1.3" }, +] [package.metadata.requires-dev] dev = [ { name = "pytest", specifier = ">=9.0.0" }, { name = "ruff", specifier = ">=0.14.4" }, ] + +[[package]] +name = "werkzeug" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/ea/b0f8eeb287f8df9066e56e831c7824ac6bab645dd6c7a8f4b2d767944f9b/werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e", size = 864687, upload-time = "2025-11-29T02:15:22.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/f9/9e082990c2585c744734f85bec79b5dae5df9c974ffee58fe421652c8e91/werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905", size = 224960, upload-time = "2025-11-29T02:15:21.13Z" }, +] From 70c9437b18f70d3d48889aefc55fbaf9c907d3ea Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 21:12:39 +0100 Subject: [PATCH 10/26] feat: improved script to compile go binaries --- go/bridge/compileBridges.sh | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/go/bridge/compileBridges.sh b/go/bridge/compileBridges.sh index afb1418..ba041db 100644 --- a/go/bridge/compileBridges.sh +++ b/go/bridge/compileBridges.sh @@ -8,10 +8,29 @@ mkdir -p "$BIN_DIR" # Path to Go bridge BRIDGE_PATH="./go/bridge/" -echo "Building Linux..." -GOOS=linux GOARCH=amd64 go build -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PATH" +# Read the mode from the first argument +MODE=$1 -echo "Building Windows..." -GOOS=windows GOARCH=amd64 go build -o "$BIN_DIR/teamsClientLib_windows.exe" "$BRIDGE_PATH" +if [[ "$MODE" == "real" ]]; then + echo "=== Building REAL mode (Production) ===" + + echo "Building Linux (real)..." + GOOS=linux GOARCH=amd64 go build -tags real -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PATH" + + echo "Building Windows (real)..." + GOOS=windows GOARCH=amd64 go build -tags real -o "$BIN_DIR/teamsClientLib_windows.exe" "$BRIDGE_PATH" + +elif [[ "$MODE" == "fake" ]]; then + echo "=== Building FAKE mode (Integration Tests) ===" + + echo "Building Linux (fake)..." + GOOS=linux GOARCH=amd64 go build -tags fake -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PATH" + +else + echo "Error: Invalid argument. Usage: $0 [real|fake]" + echo " real - builds for Linux and Windows with production code" + echo " fake - builds for Linux only with mock capability" + exit 1 +fi echo "Done! Binaries saved in $BIN_DIR" \ No newline at end of file From 851ddca7e1e3fa209803fdb5e135a4207dff3425 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 21:20:32 +0100 Subject: [PATCH 11/26] refactor: detached fake client init function to tests folder --- python/teams_lib_pzsp2_z1/client.py | 5 +++-- python/tests/init_fake_client.py | 16 ++++++++++++++++ python/tests/test_channels.py | 19 ++++++++++++------- 3 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 python/tests/init_fake_client.py diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index 1c75188..41651d9 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -10,7 +10,7 @@ class TeamsClient: - def __init__(self): + def __init__(self, auto_init: bool = True): self._lock = threading.Lock() self.proc = subprocess.Popen( # noqa: S603 @@ -24,7 +24,8 @@ def __init__(self): self.channels = ChannelsService(self) - self.init_client() + if auto_init: + self.init_client() def _binary(self): base = pathlib.Path(__file__).parent / "bin" diff --git a/python/tests/init_fake_client.py b/python/tests/init_fake_client.py new file mode 100644 index 0000000..1cda919 --- /dev/null +++ b/python/tests/init_fake_client.py @@ -0,0 +1,16 @@ +from typing import Any +from teams_lib_pzsp2_z1 import config +from teams_lib_pzsp2_z1.client import TeamsClient + +def init_fake_client(client: TeamsClient, mock_server_url: str) -> Any: + """ + Helper function to initialize the Go client in FAKE mode. + It injects the mockServerUrl. + """ + + return client.execute( + cmd_type="init", + params={ + "mockServerUrl": mock_server_url, + } + ) \ No newline at end of file diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 51dcf3b..ca943d3 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -1,4 +1,5 @@ from teams_lib_pzsp2_z1.client import TeamsClient +from tests.init_fake_client import init_fake_client def test_list_channels_integration(httpserver): @@ -27,14 +28,18 @@ def test_list_channels_integration(httpserver): ).respond_with_json(ms_graph_response) # Init fake client - client = TeamsClient() - client.init_fake_client(httpserver.url_for("")) + client = TeamsClient(auto_init=False) + try: + init_fake_client(client, httpserver.url_for("")) - channels = client.channels.list_channels(fake_team_id) + channels = client.channels.list_channels("team-123") - assert len(channels) == 2 + assert len(channels) == 2 - first_channel = channels[0] + first_channel = channels[0] - assert first_channel["displayName"] == "General" - assert first_channel["id"] == "19:123123@thread.tacv2" + assert first_channel["displayName"] == "General" + assert first_channel["id"] == "19:123123@thread.tacv2" + + finally: + client.close() From 9fd184a6258f98e14fb6b8a8b61e41b4bcbf5308 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Thu, 11 Dec 2025 21:23:21 +0100 Subject: [PATCH 12/26] fix: compiling script --- go/bridge/compileBridges.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) mode change 100644 => 100755 go/bridge/compileBridges.sh diff --git a/go/bridge/compileBridges.sh b/go/bridge/compileBridges.sh old mode 100644 new mode 100755 index ba041db..b058e5c --- a/go/bridge/compileBridges.sh +++ b/go/bridge/compileBridges.sh @@ -1,12 +1,18 @@ #!/bin/bash set -e -# Directory to save the compiled binaries -BIN_DIR="./python/teams_lib_pzsp2_z1/bin" +# 1. Zapamiętajmy gdzie jesteśmy (katalog główny projektu) +PROJECT_ROOT=$(pwd) + +# 2. Definiujemy ścieżkę wyjściową (absolutną, żeby działała po zmianie katalogu) +BIN_DIR="$PROJECT_ROOT/python/teams_lib_pzsp2_z1/bin" mkdir -p "$BIN_DIR" -# Path to Go bridge -BRIDGE_PATH="./go/bridge/" +# 3. Wchodzimy do katalogu 'go', gdzie leży plik go.mod +cd go + +# Ścieżka do pakietu mostka (teraz relatywna względem katalogu 'go') +BRIDGE_PKG="./bridge" # Read the mode from the first argument MODE=$1 @@ -15,16 +21,16 @@ if [[ "$MODE" == "real" ]]; then echo "=== Building REAL mode (Production) ===" echo "Building Linux (real)..." - GOOS=linux GOARCH=amd64 go build -tags real -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PATH" + GOOS=linux GOARCH=amd64 go build -tags real -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PKG" echo "Building Windows (real)..." - GOOS=windows GOARCH=amd64 go build -tags real -o "$BIN_DIR/teamsClientLib_windows.exe" "$BRIDGE_PATH" + GOOS=windows GOARCH=amd64 go build -tags real -o "$BIN_DIR/teamsClientLib_windows.exe" "$BRIDGE_PKG" elif [[ "$MODE" == "fake" ]]; then echo "=== Building FAKE mode (Integration Tests) ===" echo "Building Linux (fake)..." - GOOS=linux GOARCH=amd64 go build -tags fake -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PATH" + GOOS=linux GOARCH=amd64 go build -tags fake -o "$BIN_DIR/teamsClientLib_linux" "$BRIDGE_PKG" else echo "Error: Invalid argument. Usage: $0 [real|fake]" From d464739ce593a99a692a4417f5dd52529aef5801 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 10:59:33 +0100 Subject: [PATCH 13/26] fix: improved test --- python/teams_lib_pzsp2_z1/services/channels.py | 2 +- python/tests/test_channels.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index adb51e5..2d1b61b 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -16,7 +16,7 @@ def list_channels(self, teamRef: str) -> list[Channel]: Channel( ID=channel["id"], Name=channel["displayName"], - IsGeneral=(channel["displayName"].lower() == "general"), + IsGeneral=(True if channel["isGeneral"] else False), ) for channel in channels_data ] diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index ca943d3..9f7a488 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -36,10 +36,13 @@ def test_list_channels_integration(httpserver): assert len(channels) == 2 - first_channel = channels[0] + assert channels[0].Name == "General" + assert channels[0].ID == "19:123123@thread.tacv2" + assert channels[0].IsGeneral == True - assert first_channel["displayName"] == "General" - assert first_channel["id"] == "19:123123@thread.tacv2" + assert channels[1].Name == "Development" + assert channels[1].ID == "19:999999@thread.tacv2" + assert channels[1].IsGeneral == False finally: client.close() From 5e89037fc0f002547282b864eff427a9e89989b2 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 11:19:12 +0100 Subject: [PATCH 14/26] ci: added basic verstion of integration tests ci --- .github/workflows/integration-tests.yml | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/integration-tests.yml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 0000000..2557728 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,63 @@ +name: Python Integeration Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + repository_dispatch: + types: [verify-new-go-lib-version] + +jobs: + build-fake: + name: Build fake client + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version-file: './go/go.mod' + check-latest: true + - run: go version + + - name: Update Go Dependency (If triggered by Go Repo) + if: github.event_name == 'repository_dispatch' + run: | + echo "Triggered by Go Core commit: ${{ github.event.client_payload.go_ref }}" + cd go + go get github.com/pzsp-teams/lib@${{ github.event.client_payload.go_ref }} + go mod tidy + + - name: Build Bridge (Fake) + run: | + chmod +x ./go/bridge/compileBridges.sh + ./go/bridge/compileBridges.sh fake + + test-integration: + name: Run Integration Tests + runs-on: ubuntu-latest + needs: build-fake + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.14' + + - name: Install dependencies + run: | + cd python + pip install pytest pytest-httpserver -r requirements.txt + pip install -e . + + - name: Run Integration Tests + run: | + cd python + pytest -v tests/integration_tests/ \ No newline at end of file From e202ed82ef24ac725bab025c5e0b28b2243e874f Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 11:26:53 +0100 Subject: [PATCH 15/26] ci: improved integration tests to use uv --- .github/workflows/integration-tests.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 2557728..54add51 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -46,18 +46,21 @@ jobs: - name: Checkout repository uses: actions/checkout@v5 - - name: Setup Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v5 with: - python-version: '3.14' + enable-cache: true + cache-dependency-glob: "python/uv.lock" + + - name: Setup Python + run: uv python install 3.12 - name: Install dependencies run: | cd python - pip install pytest pytest-httpserver -r requirements.txt - pip install -e . + uv sync --dev - name: Run Integration Tests run: | cd python - pytest -v tests/integration_tests/ \ No newline at end of file + uv run pytest -v tests/integration_tests/ \ No newline at end of file From 0905f79ff301aed9d7747c5903c768882d9582bf Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 11:56:13 +0100 Subject: [PATCH 16/26] ci: improved passing fake binary lib through stages --- .github/workflows/integration-tests.yml | 23 ++++++++++++++++++++--- python/MANIFEST.in | 2 +- python/pyproject.toml | 3 +++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 54add51..8aec4da 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -10,13 +10,16 @@ on: repository_dispatch: types: [verify-new-go-lib-version] +permissions: + contents: write + jobs: build-fake: name: Build fake client runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v4 - name: Setup go uses: actions/setup-go@v5 @@ -33,18 +36,31 @@ jobs: go get github.com/pzsp-teams/lib@${{ github.event.client_payload.go_ref }} go mod tidy - - name: Build Bridge (Fake) + - name: Compile Fake Binary run: | chmod +x ./go/bridge/compileBridges.sh ./go/bridge/compileBridges.sh fake + - name: Upload Fake Binary + uses: actions/upload-artifact@v4 + with: + name: fake-binary + path: python/teams_lib_pzsp2_z1/bin/ + retention-days: 1 + test-integration: name: Run Integration Tests runs-on: ubuntu-latest needs: build-fake steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v4 + + - name: Download Fake Binary + uses: actions/download-artifact@v4 + with: + name: fake-binary + path: python/teams_lib_pzsp2_z1/bin/ - name: Install uv uses: astral-sh/setup-uv@v5 @@ -62,5 +78,6 @@ jobs: - name: Run Integration Tests run: | + chmod +x python/teams_lib_pzsp2_z1/bin/* cd python uv run pytest -v tests/integration_tests/ \ No newline at end of file diff --git a/python/MANIFEST.in b/python/MANIFEST.in index cd81339..44af821 100644 --- a/python/MANIFEST.in +++ b/python/MANIFEST.in @@ -1 +1 @@ -include teams_lib_pzsp2_z1/bin/* \ No newline at end of file +recursive-include teams_lib_pzsp2_z1/bin * \ No newline at end of file diff --git a/python/pyproject.toml b/python/pyproject.toml index 6835cb2..74dfadb 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -27,3 +27,6 @@ exclude = ["test_*.py", "tests/*"] [tool.ruff.lint] extend-select = ["I", "E", "W", "F", "C90", "B", "S", "UP", "PL"] + +[tool.setuptools] +include-package-data = true From 64d4010e6b563c1048995cccfce0cc52901088f3 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 12:15:20 +0100 Subject: [PATCH 17/26] fix: Fixed first integration test --- .github/workflows/integration-tests.yml | 3 ++- .gitignore | 6 ++++- go/go.mod | 6 ++++- go/go.sum | 10 +++++++++ go/internal/json-client/fake-client.go | 16 ++++++++------ .../teams_lib_pzsp2_z1/services/channels.py | 9 ++++---- python/tests/__init__.py | 0 python/tests/test_channels.py | 22 +++++++++++++++---- 8 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 python/tests/__init__.py diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 8aec4da..7ccc874 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -80,4 +80,5 @@ jobs: run: | chmod +x python/teams_lib_pzsp2_z1/bin/* cd python - uv run pytest -v tests/integration_tests/ \ No newline at end of file + uv pip install -e . + uv run pytest -v tests/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7e99e36..c53ce4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -*.pyc \ No newline at end of file +*.pyc +.egg-info +python/teams_lib_pzsp2_z1.egg-info +python/teams_lib_pzsp2_z1/bin/* + diff --git a/go/go.mod b/go/go.mod index 89d73c1..75df08b 100644 --- a/go/go.mod +++ b/go/go.mod @@ -5,16 +5,19 @@ go 1.25.5 require ( github.com/microsoft/kiota-abstractions-go v1.9.3 github.com/microsoftgraph/msgraph-sdk-go v1.90.0 - github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d + github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f ) require ( + al.essio.dev/pkg/shellescape v1.5.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/danieljoos/wincred v1.2.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 // indirect @@ -28,6 +31,7 @@ require ( github.com/microsoftgraph/msgraph-sdk-go-core v1.4.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect + github.com/zalando/go-keyring v0.2.6 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect diff --git a/go/go.sum b/go/go.sum index 0f0c6b9..e345e5c 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,3 +1,5 @@ +al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= +al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= @@ -6,6 +8,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= +github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -13,6 +17,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -51,12 +57,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d h1:hFdPrkbnUngxrGdMkPVZpvY76T6QgnXo87u9wzXgNWY= github.com/pzsp-teams/lib v0.0.0-20251208073120-ca1606f2b27d/go.mod h1:uGrgoO+KmBc55Le/AeysA0d24HOCQHO55Uw5Z7naQo4= +github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f h1:tmXKJEWYl0f/H4n91wnfW1asgkHbftKgX5nbpafNHmU= +github.com/pzsp-teams/lib v0.0.0-20251213104555-e41d3adf599f/go.mod h1:EPtQVPz+HYZNsSpcoQ3QWLbMEz1O2qeIH5ojlYJLEic= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= +github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go index 16db332..f26522c 100644 --- a/go/internal/json-client/fake-client.go +++ b/go/internal/json-client/fake-client.go @@ -4,10 +4,10 @@ package jsonclient import ( "context" - "fmt" "crypto/tls" + "fmt" "net/http" - "strings" + "net/url" azcore "github.com/microsoft/kiota-abstractions-go" graph "github.com/microsoftgraph/msgraph-sdk-go" @@ -35,11 +35,13 @@ type HijackTransport struct { } func (t *HijackTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.URL.Scheme = "http" + parsed, err := url.Parse(t.MockServerURL) + if err != nil { + return nil, err + } - targetHost := strings.TrimPrefix(t.MockServerURL, "http://") - targetHost = strings.TrimPrefix(targetHost, "https://") - req.URL.Host = targetHost + req.URL.Scheme = parsed.Scheme + req.URL.Host = parsed.Host return t.Transport.RoundTrip(req) } @@ -49,7 +51,7 @@ func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { // Parse parameters mockServerURL, ok := req.Params["mockServerUrl"].(string) if !ok || mockServerURL == "" { - return nil, fmt.Errorf("invalid mockServerUrl parameter") + return nil, fmt.Errorf("invalid mockServerUrl parameter") } // A. Konfiguracja SenderConfig (tak jak w wersji Real) diff --git a/python/teams_lib_pzsp2_z1/services/channels.py b/python/teams_lib_pzsp2_z1/services/channels.py index 2d1b61b..303e74e 100644 --- a/python/teams_lib_pzsp2_z1/services/channels.py +++ b/python/teams_lib_pzsp2_z1/services/channels.py @@ -11,12 +11,11 @@ def list_channels(self, teamRef: str) -> list[Channel]: "teamRef": teamRef, }, ) - channels_data = response.get("channels", []) return [ Channel( - ID=channel["id"], - Name=channel["displayName"], - IsGeneral=(True if channel["isGeneral"] else False), + ID=channel["ID"], + Name=channel["Name"], + IsGeneral=(True if channel["IsGeneral"] else False), ) - for channel in channels_data + for channel in response ] diff --git a/python/tests/__init__.py b/python/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 9f7a488..1c84cf5 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -7,9 +7,20 @@ def test_list_channels_integration(httpserver): Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server """ # Fake server config + fake_team_name = "Test Team" fake_team_id = "team-123-abc" - ms_graph_response = { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('team-123-abc')/channels", + ms_graph_response_teams = { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams", + "value": [ + { + "id": fake_team_id, + "displayName": fake_team_name, + "description": "A team for testing", + }, + ], + } + ms_graph_response_channels = { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{fake_team_id}')/channels", "value": [ { "id": "19:123123@thread.tacv2", @@ -23,16 +34,19 @@ def test_list_channels_integration(httpserver): }, ], } + httpserver.expect_request( + "/v1.0/users/me-token-to-replace/joinedTeams", method="GET" + ).respond_with_json(ms_graph_response_teams) httpserver.expect_request( f"/v1.0/teams/{fake_team_id}/channels", method="GET" - ).respond_with_json(ms_graph_response) + ).respond_with_json(ms_graph_response_channels) # Init fake client client = TeamsClient(auto_init=False) try: init_fake_client(client, httpserver.url_for("")) - channels = client.channels.list_channels("team-123") + channels = client.channels.list_channels("Test Team") assert len(channels) == 2 From d04b59c48d89d502eeea7d6ee6ce70da99c2e8c7 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 12:32:30 +0100 Subject: [PATCH 18/26] refactor: Adjusted creating client to manage cache --- go/internal/json-client/real-client.go | 17 +++- go/internal/json-model/json-model.go | 103 +++++++++++++++---------- python/teams_lib_pzsp2_z1/client.py | 15 +++- 3 files changed, 90 insertions(+), 45 deletions(-) diff --git a/go/internal/json-client/real-client.go b/go/internal/json-client/real-client.go index f246d2f..a00178c 100644 --- a/go/internal/json-client/real-client.go +++ b/go/internal/json-client/real-client.go @@ -20,7 +20,22 @@ func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { return nil, err } - client, err := lib.NewClient(context.TODO(), &authConfig, &senderConfig) + cacheEnabled, err := jsonModel.ParseCacheEnabled(req.Params["cacheEnabled"]) + if err != nil { + return nil, err + } + + var cachePath *string + if cacheEnabled { + path, err := jsonModel.ParseCachePath(req.Params["cachePath"]) + if err != nil { + return nil, err + } + cachePath = path + } + + + client, err := lib.NewClient(context.TODO(), authConfig, senderConfig, cacheEnabled, cachePath) if err != nil { return nil, err } diff --git a/go/internal/json-model/json-model.go b/go/internal/json-model/json-model.go index a2d4c5d..3d76018 100644 --- a/go/internal/json-model/json-model.go +++ b/go/internal/json-model/json-model.go @@ -16,6 +16,8 @@ type Request struct { type Config struct { SenderConfigMap SenderConfigMap `json:"senderConfig"` AuthConfigMap AuthConfigMap `json:"authConfig"` + CacheEnabled bool `json:"cacheEnabled"` + CachePath string `json:"cachePath,omitempty"` } type SenderConfigMap struct { @@ -24,22 +26,22 @@ type SenderConfigMap struct { Timeout int `json:"timeout"` } -func (scm SenderConfigMap) ToSenderConfig() (lib.SenderConfig, error) { - if scm.MaxRetries < 0 { - return lib.SenderConfig{}, fmt.Errorf("maxRetries cannot be negative") - } - if scm.NextRetryDelay < 0 { - return lib.SenderConfig{}, fmt.Errorf("nextRetryDelay cannot be negative") - } - if scm.Timeout <= 0 { - return lib.SenderConfig{}, fmt.Errorf("timeout must be > 0") - } +func (scm SenderConfigMap) ToSenderConfig() (*lib.SenderConfig, error) { + if scm.MaxRetries < 0 { + return nil, fmt.Errorf("maxRetries cannot be negative") + } + if scm.NextRetryDelay < 0 { + return nil, fmt.Errorf("nextRetryDelay cannot be negative") + } + if scm.Timeout <= 0 { + return nil, fmt.Errorf("timeout must be > 0") + } - return lib.SenderConfig{ - MaxRetries: scm.MaxRetries, - NextRetryDelay: scm.NextRetryDelay, - Timeout: scm.Timeout, - }, nil + return &lib.SenderConfig{ + MaxRetries: scm.MaxRetries, + NextRetryDelay: scm.NextRetryDelay, + Timeout: scm.Timeout, + }, nil } type AuthConfigMap struct { @@ -50,35 +52,55 @@ type AuthConfigMap struct { AuthMethod string `json:"authMethod"` } -func (acm AuthConfigMap) ToAuthConfig() (lib.AuthConfig, error) { - if acm.ClientID == "" { - return lib.AuthConfig{}, fmt.Errorf("clientId is required") - } - if acm.Tenant == "" { - return lib.AuthConfig{}, fmt.Errorf("tenant is required") - } - if acm.Email == "" { - return lib.AuthConfig{}, fmt.Errorf("email is required") - } - if len(acm.Scopes) == 0 { - return lib.AuthConfig{}, fmt.Errorf("scopes cannot be empty") - } - if acm.AuthMethod == "" { - return lib.AuthConfig{}, fmt.Errorf("authMethod is required") +func (acm AuthConfigMap) ToAuthConfig() (*lib.AuthConfig, error) { + if acm.ClientID == "" { + return nil, fmt.Errorf("clientId is required") + } + if acm.Tenant == "" { + return nil, fmt.Errorf("tenant is required") + } + if acm.Email == "" { + return nil, fmt.Errorf("email is required") + } + if len(acm.Scopes) == 0 { + return nil, fmt.Errorf("scopes cannot be empty") + } + if acm.AuthMethod == "" { + return nil, fmt.Errorf("authMethod is required") + } + + authMethod, err := validateAuthMethod(acm.AuthMethod) + if err != nil { + return nil, err + } + + return &lib.AuthConfig{ + ClientID: acm.ClientID, + Tenant: acm.Tenant, + Email: acm.Email, + Scopes: acm.Scopes, + AuthMethod: authMethod, + }, nil +} + +func ParseCacheEnabled(value interface{}) (bool, error) { + if value == nil { + return false, nil // default } - authMethod, err := validateAuthMethod(acm.AuthMethod) - if err != nil { - return lib.AuthConfig{}, err + enabled, ok := value.(bool) + if !ok { + return false, fmt.Errorf("cacheEnabled must be boolean") } + return enabled, nil +} - return lib.AuthConfig{ - ClientID: acm.ClientID, - Tenant: acm.Tenant, - Email: acm.Email, - Scopes: acm.Scopes, - AuthMethod: authMethod, - }, nil +func ParseCachePath(value interface{}) (*string, error) { + path, ok := value.(string) + if !ok || path == "" { + return nil, fmt.Errorf("cachePath must be a non-empty string") + } + return &path, nil } func validateAuthMethod(method string) (string, error) { @@ -87,4 +109,3 @@ func validateAuthMethod(method string) (string, error) { } return "", fmt.Errorf("invalid auth method: %s", method) } - diff --git a/python/teams_lib_pzsp2_z1/client.py b/python/teams_lib_pzsp2_z1/client.py index 41651d9..584d1f1 100644 --- a/python/teams_lib_pzsp2_z1/client.py +++ b/python/teams_lib_pzsp2_z1/client.py @@ -10,7 +10,12 @@ class TeamsClient: - def __init__(self, auto_init: bool = True): + def __init__( + self, + auto_init: bool = True, + cache_enabled: bool = False, + cache_path: str | None = None, + ): self._lock = threading.Lock() self.proc = subprocess.Popen( # noqa: S603 @@ -25,7 +30,7 @@ def __init__(self, auto_init: bool = True): self.channels = ChannelsService(self) if auto_init: - self.init_client() + self.init_client(cache_enabled, cache_path) def _binary(self): base = pathlib.Path(__file__).parent / "bin" @@ -38,7 +43,9 @@ def _binary(self): else: raise RuntimeError("Unsupported OS") - def init_client(self) -> Any: + def init_client( + self, cache_enabled: bool = False, cache_path: str | None = None + ) -> Any: sender_config = config.SenderConfig() auth_config = config.load_auth_config() return self.execute( @@ -56,6 +63,8 @@ def init_client(self) -> Any: "scopes": auth_config.scopes, "authMethod": auth_config.auth_method, }, + "cacheEnabled": cache_enabled, + "cachePath": cache_path, }, ) From 3579ae9bc763970eb002cd76f5ab3b250d00c0f4 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 12:49:08 +0100 Subject: [PATCH 19/26] refactor: improved setting up fake MS teams API in integration tests --- python/teams_lib_pzsp2_z1/model/team.py | 10 ++++ python/tests/fake_server/data.py | 61 +++++++++++++++++++++++++ python/tests/fake_server/setup.py | 21 +++++++++ python/tests/test_channels.py | 53 +++++---------------- 4 files changed, 103 insertions(+), 42 deletions(-) create mode 100644 python/teams_lib_pzsp2_z1/model/team.py create mode 100644 python/tests/fake_server/data.py create mode 100644 python/tests/fake_server/setup.py diff --git a/python/teams_lib_pzsp2_z1/model/team.py b/python/teams_lib_pzsp2_z1/model/team.py new file mode 100644 index 0000000..9de6e1b --- /dev/null +++ b/python/teams_lib_pzsp2_z1/model/team.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass + + +@dataclass +class Team: + ID: str + DisplayName: str + Description: str + IsArchived: bool + Visibility: str diff --git a/python/tests/fake_server/data.py b/python/tests/fake_server/data.py new file mode 100644 index 0000000..d917f45 --- /dev/null +++ b/python/tests/fake_server/data.py @@ -0,0 +1,61 @@ +from dataclasses import dataclass +from teams_lib_pzsp2_z1.model.team import Team +from teams_lib_pzsp2_z1.model.channel import Channel + +@dataclass +class FakeServerData: + teams: list[Team] + channels: dict[str, list[Channel]] + + def __init__(self) -> FakeServerData: + self.teams = [ + Team( + ID="team-123-abc", + DisplayName="Test Team", + Description="A team for testing", + IsArchived=False, + Visibility="Private", + ), + ] + self.channels = { + "team-123-abc": [ + Channel( + ID="19:123123@thread.tacv2", + Name="General", + IsGeneral=True, + ), + Channel( + ID="19:999999@thread.tacv2", + Name="Development", + IsGeneral=False, + ), + ], + } + + def get_myJoinedTeams_response(self) -> dict: + return { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams", + "value": [ + { + "id": team.ID, + "displayName": team.DisplayName, + "description": team.Description, + "isArchived": team.IsArchived, + "visibility": team.Visibility, + } + for team in self.teams + ], + } + + def get_listChannels_response(self, team_id: str) -> dict: + return { + "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{team_id}')/channels", + "value": [ + { + "id": channel.ID, + "displayName": channel.Name, + "isGeneral": channel.IsGeneral, + } + for channel in self.channels.get(team_id, []) + ], + } \ No newline at end of file diff --git a/python/tests/fake_server/setup.py b/python/tests/fake_server/setup.py new file mode 100644 index 0000000..aa92527 --- /dev/null +++ b/python/tests/fake_server/setup.py @@ -0,0 +1,21 @@ +from tests.fake_server.data import FakeServerData + +def setup_fake_server(httpserver) -> FakeServerData: + """ + Sets up a fake HTTP server to simulate Microsoft Graph API responses. + """ + + data = FakeServerData() + + # Mock response for listing joined teams + httpserver.expect_request( + "/v1.0/users/me-token-to-replace/joinedTeams", method="GET" + ).respond_with_json(data.get_myJoinedTeams_response()) + + # Mock response for listing channels in the fake team + httpserver.expect_request( + f"/v1.0/teams/{data.teams[0].ID}/channels", method="GET" + ).respond_with_json(data.get_listChannels_response(data.teams[0].ID)) + + return data + diff --git a/python/tests/test_channels.py b/python/tests/test_channels.py index 1c84cf5..121f315 100644 --- a/python/tests/test_channels.py +++ b/python/tests/test_channels.py @@ -1,62 +1,31 @@ from teams_lib_pzsp2_z1.client import TeamsClient from tests.init_fake_client import init_fake_client +from tests.fake_server.setup import setup_fake_server def test_list_channels_integration(httpserver): """ Integration test: Python -> Go Binary -> Fake HTTP -> Python Mock Server """ - # Fake server config - fake_team_name = "Test Team" - fake_team_id = "team-123-abc" - ms_graph_response_teams = { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams", - "value": [ - { - "id": fake_team_id, - "displayName": fake_team_name, - "description": "A team for testing", - }, - ], - } - ms_graph_response_channels = { - "@odata.context": f"https://graph.microsoft.com/v1.0/$metadata#teams('{fake_team_id}')/channels", - "value": [ - { - "id": "19:123123@thread.tacv2", - "displayName": "General", - "description": "General discussions", - }, - { - "id": "19:999999@thread.tacv2", - "displayName": "Development", - "description": "Coding stuff", - }, - ], - } - httpserver.expect_request( - "/v1.0/users/me-token-to-replace/joinedTeams", method="GET" - ).respond_with_json(ms_graph_response_teams) - httpserver.expect_request( - f"/v1.0/teams/{fake_team_id}/channels", method="GET" - ).respond_with_json(ms_graph_response_channels) + + data = setup_fake_server(httpserver) # Init fake client client = TeamsClient(auto_init=False) try: init_fake_client(client, httpserver.url_for("")) - channels = client.channels.list_channels("Test Team") + channels = client.channels.list_channels(data.teams[0].DisplayName) - assert len(channels) == 2 + assert len(channels) == len(data.channels[data.teams[0].ID]) - assert channels[0].Name == "General" - assert channels[0].ID == "19:123123@thread.tacv2" - assert channels[0].IsGeneral == True + assert channels[0].Name == data.channels[data.teams[0].ID][0].Name + assert channels[0].ID == data.channels[data.teams[0].ID][0].ID + assert channels[0].IsGeneral == data.channels[data.teams[0].ID][0].IsGeneral - assert channels[1].Name == "Development" - assert channels[1].ID == "19:999999@thread.tacv2" - assert channels[1].IsGeneral == False + assert channels[1].Name == data.channels[data.teams[0].ID][1].Name + assert channels[1].ID == data.channels[data.teams[0].ID][1].ID + assert channels[1].IsGeneral == data.channels[data.teams[0].ID][1].IsGeneral finally: client.close() From 72bb7a66bf02cae0ba5f3073702c4b76e1cb388e Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 12:52:52 +0100 Subject: [PATCH 20/26] docs: improved comment --- go/internal/json-client/fake-client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/go/internal/json-client/fake-client.go b/go/internal/json-client/fake-client.go index f26522c..2d731ca 100644 --- a/go/internal/json-client/fake-client.go +++ b/go/internal/json-client/fake-client.go @@ -54,7 +54,6 @@ func NewJSONClient(req jsonModel.Request) (*TeamsJSONClient, error) { return nil, fmt.Errorf("invalid mockServerUrl parameter") } - // A. Konfiguracja SenderConfig (tak jak w wersji Real) senderConfig := lib.SenderConfig{ MaxRetries: 3, NextRetryDelay: 2, From e939163945499fda74f4004140ab2613259e0677 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 13:09:38 +0100 Subject: [PATCH 21/26] ci: added build release stage in ci --- .github/workflows/integration-tests.yml | 55 ++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 7ccc874..1e10aa7 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -81,4 +81,57 @@ jobs: chmod +x python/teams_lib_pzsp2_z1/bin/* cd python uv pip install -e . - uv run pytest -v tests/ \ No newline at end of file + uv run pytest -v tests/ + + update-release-branch: + name: Update Release Branch + runs-on: ubuntu-latest + needs: test-integration + if: github.ref == 'refs/heads/main' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: './go/go.mod' + check-latest: true + + - name: Re-apply Go Dependency Update + if: github.event_name == 'repository_dispatch' + run: | + cd go + go get github.com/pzsp-teams/lib@${{ github.event.client_payload.go_ref }} + go mod tidy + + - name: Compile Real Binaries + run: | + chmod +x ./go/bridge/compileBridges.sh + ./go/bridge/compileBridges.sh real + + - name: Prepare Client Directory + run: | + mkdir python_release + cp -r python/* python_release/ + rm -rf python_release/tests + find python_release -type d -name "__pycache__" -exec rm -rf {} + + rm -rf python_release/.venv + + ls -R python_release/teams_lib_pzsp2_z1/bin + + - name: Commit and Push to Release Branch + run: | + git config --global user.name "GitHub Actions Bot" + git config --global user.email "actions@github.com" + git checkout --orphan python-release + + git rm -rf . + cp -r python_release/* . + rm -rf python_release + git add . + + git commit -m "Release build: ${{ github.sha }}" + git push -f origin python-release \ No newline at end of file From a1580aa5fdccad6719c45e8733d3084444c72632 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 13:14:07 +0100 Subject: [PATCH 22/26] ci: added pipeline to aumatically release to pip --- .github/workflows/publish.yml | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..b8eae2e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,38 @@ +name: Publish to PyPI + +on: + push: + branches: + - python-release + +jobs: + pypi-publish: + name: Upload release to PyPI + runs-on: ubuntu-latest + + permissions: + contents: read + # id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Setup Python + run: uv python install 3.12 + + - name: Install build tools + run: | + uv pip install --system build + + - name: Build the package + run: uv run python -m build + + # - name: Publish to PyPI + # uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file From f2ed6c6dbd6ed2a500f58d1ef9677eb27a0a3f0e Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 13:27:25 +0100 Subject: [PATCH 23/26] ci: added another stage to automatically bump version --- .github/workflows/integration-tests.yml | 65 ++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 1e10aa7..5c9847f 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1,4 +1,4 @@ -name: Python Integeration Tests +name: Python Integration Tests on: push: @@ -14,6 +14,9 @@ permissions: contents: write jobs: + # ==================================================== + # 1. BUILD FAKE + # ==================================================== build-fake: name: Build fake client runs-on: ubuntu-latest @@ -48,6 +51,9 @@ jobs: path: python/teams_lib_pzsp2_z1/bin/ retention-days: 1 + # ==================================================== + # 2. RUN TESTS + # ==================================================== test-integration: name: Run Integration Tests runs-on: ubuntu-latest @@ -83,10 +89,59 @@ jobs: uv pip install -e . uv run pytest -v tests/ + # ==================================================== + # 3. BUMP VERSION (COMMIT TO MAIN) + # ==================================================== + bump-version: + name: Bump Version on Main + runs-on: ubuntu-latest + needs: test-integration + if: github.ref == 'refs/heads/main' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Bump Patch Version + run: | + cd python + python3 -c " + import re + file_path = 'pyproject.toml' + with open(file_path, 'r') as f: + content = f.read() + match = re.search(r'version = \"(\d+\.\d+)\.(\d+)\"', content) + if match: + prefix = match.group(1) + patch = int(match.group(2)) + new_version = f'{prefix}.{patch + 1}' + new_content = re.sub(r'version = \"\d+\.\d+\.\d+\"', f'version = \"{new_version}\"', content) + with open(file_path, 'w') as f: + f.write(new_content) + print(f'Bumped version to {new_version}') + else: + print('Version not found or invalid format') + exit(1) + " + + - name: Commit and Push to Main + run: | + git config --global user.name "GitHub Actions Bot" + git config --global user.email "actions@github.com" + + git add python/pyproject.toml + git commit -m "Bump version [skip ci]" + git push origin main + + # ==================================================== + # 4. PREPARE RELEASE BRANCH + # ==================================================== update-release-branch: name: Update Release Branch runs-on: ubuntu-latest - needs: test-integration + needs: bump-version if: github.ref == 'refs/heads/main' steps: - name: Checkout repository @@ -94,6 +149,9 @@ jobs: with: fetch-depth: 0 + - name: Pull latest version from main + run: git pull origin main + - name: Setup Go uses: actions/setup-go@v5 with: @@ -116,6 +174,9 @@ jobs: run: | mkdir python_release cp -r python/* python_release/ + + grep "version =" python_release/pyproject.toml + rm -rf python_release/tests find python_release -type d -name "__pycache__" -exec rm -rf {} + rm -rf python_release/.venv From e5263a00aa95a96628175e082f337dacf5d2aaf1 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 14:51:55 +0100 Subject: [PATCH 24/26] ci: corrected publishing and enabled main pushes --- .github/workflows/integration-tests.yml | 8 ++++---- .github/workflows/publish.yml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 5c9847f..2215bac 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -27,7 +27,7 @@ jobs: - name: Setup go uses: actions/setup-go@v5 with: - go-version-file: './go/go.mod' + go-version-file: "./go/go.mod" check-latest: true - run: go version @@ -101,7 +101,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.ADMIN_PAT }} fetch-depth: 0 - name: Bump Patch Version @@ -155,7 +155,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version-file: './go/go.mod' + go-version-file: "./go/go.mod" check-latest: true - name: Re-apply Go Dependency Update @@ -195,4 +195,4 @@ jobs: git add . git commit -m "Release build: ${{ github.sha }}" - git push -f origin python-release \ No newline at end of file + git push -f origin python-release diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b8eae2e..d60e2ce 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: read - # id-token: write + id-token: write steps: - name: Checkout repository @@ -34,5 +34,5 @@ jobs: - name: Build the package run: uv run python -m build - # - name: Publish to PyPI - # uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file From f4e35d15eebbfa0c280d71e8b398a013325de360 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sat, 13 Dec 2025 15:10:42 +0100 Subject: [PATCH 25/26] fix: adjusted git ignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c53ce4c..d4d4235 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .egg-info python/teams_lib_pzsp2_z1.egg-info python/teams_lib_pzsp2_z1/bin/* - +.env +example.py From ff3e86b93bbb495340afbe2a55647aced6f033c7 Mon Sep 17 00:00:00 2001 From: Michal <01187288@pw.edu.pl> Date: Sun, 14 Dec 2025 17:45:09 +0100 Subject: [PATCH 26/26] chore: removed commented dead code --- go/internal/json-client/teams-json-client.go | 24 -------------------- 1 file changed, 24 deletions(-) diff --git a/go/internal/json-client/teams-json-client.go b/go/internal/json-client/teams-json-client.go index 237356a..afe9430 100644 --- a/go/internal/json-client/teams-json-client.go +++ b/go/internal/json-client/teams-json-client.go @@ -1,33 +1,9 @@ package jsonclient import ( - // "context" - lib "github.com/pzsp-teams/lib" - // models "github.com/pzsp-teams/lib/models" ) type TeamsJSONClient struct { client *lib.Client } - -// type teamsService interface { -// ListMyJoined(ctx context.Context) ([]*models.Team, error) -// } - -// type channelsService interface { -// ListChannels(ctx context.Context, team string) ([]*models.Channel, error) -// ListMessages(ctx context.Context, team, channel string, opts *models.ListMessagesOptions) ([]*models.Message, error) -// } - -// type clientAdapter struct { -// client *lib.Client -// } - -// func (c *clientAdapter) Teams() teamsService { -// return c.client.Teams -// } - -// func (c *clientAdapter) Channels() channelsService { -// return c.client.Channels -// }