diff --git a/.convcommit b/.convcommit new file mode 100644 index 0000000..0eed165 --- /dev/null +++ b/.convcommit @@ -0,0 +1,76 @@ +# convcommit - Conventional Commit message builder +# This file is read by the `convcommit` CLI tool to populate +# the interactive selector menus. +# Commit this file to share the project's commit vocabulary with the team. +# +# FORMAT +# type: — commit type option (e.g. fix, feat, docs) +# scope: — commit scope option +# message: — commit message template +# +# SPECIAL PREFIXES +# ~ — marks the default selection +# _ — enables free-text manual input (press ".") +# [X] — forces key letter X for this entry (e.g. [B]build, [W]wip) +# +# HOW TO USE (interactive) +# Run `convcommit` in a git repo. A menu appears for type, scope, message. +# Press the letter in brackets [A][B]... or [.] for free-text input. +# Stage and push in one shot: +# convcommit -a -p +# +# HOW TO USE (direct flags — scripts, AI agents) +# Bypass the selector entirely with explicit flags: +# convcommit --type fix --scope auth --message "fix null pointer" --push +# convcommit -t feat -s api -m "add endpoint" -a -p +# +# SMART PATTERN — stage specific files and commit in one command +# Use --add instead of nested command substitution. +# Anti-pattern (avoid): +# msg=$(convcommit --type fix --message "fix") && git commit -m "$msg" && git push +# Recommended: +# convcommit --add src/auth.sh --type fix --scope auth --message "fix null pointer" --push +# Stage multiple files: +# convcommit --add src/auth.sh --add tests/auth_test.sh -t test -s auth -m "add tests" -p +# +# HOW TO USE (pipe / non-interactive) +# Pipe selections as lines: one per stage (type, scope, message). +# Use the letter shown in the menu, or "." to trigger free-text input. +# Examples: +# printf "G\n.\nfix null pointer in login\n" | convcommit +# printf "F\n\nadd endpoint\n" | convcommit -a -p +# Capture just the formatted message: +# msg=$(printf "G\n\nfix null pointer\n" | convcommit) +# +# OTHER USEFUL FLAGS +# --reset Regenerate this file with the latest defaults +# --help Show all options +# +# INSTALLATION +# convcommit is a single bash file with no dependencies. +# Install it locally in your project: +# curl -fsSL https://raw.githubusercontent.com/francescobianco/convcommit/refs/heads/main/bin/convcommit \ +# -o bin/convcommit && chmod +x bin/convcommit +# Or system-wide: +# curl -fsSL https://raw.githubusercontent.com/francescobianco/convcommit/refs/heads/main/bin/convcommit \ +# -o /usr/local/bin/convcommit && chmod +x /usr/local/bin/convcommit +type:[B]build +type:~chore +type:[D]docs +type:deps +type:feat +type:fix +type:ci +type:init +type:merge +type:perf +type:refactor +type:revert +type:security +type:style +type:test +type:[W]wip +scope:_ +scope:~ +message:_ +message:~_ diff --git a/.github/assets/repo-header-a3.png b/.github/assets/repo-header-a3.png new file mode 100644 index 0000000..c508353 Binary files /dev/null and b/.github/assets/repo-header-a3.png differ diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..ad1363d --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,26 @@ + +name: build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.gitignore b/.gitignore index d8b3388..d6bb5b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# IDEs and editors +.idea/ +.vscode/ + # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9f074c6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Openapi® + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9e2eb78 --- /dev/null +++ b/Makefile @@ -0,0 +1,86 @@ +#!make + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# ____ _ # +# / __ \____ ___ ____ ____ _____ (_) ® # +# / / / / __ \/ _ \/ __ \/ __ `/ __ \/ / # +# / /_/ / /_/ / __/ / / / /_/ / /_/ / / # +# \____/ .___/\___/_/ /_/\__,_/ .___/_/ # +# /_/ /_/ # +# # +# The Largest Certified API Marketplace # +# Accelerate Digital Transformation • Simplify Processes • Lead Industry # +# # +# ═══════════════════════════════════════════════════════════════════════ # +# # +# Project: openapi-go-sdk # +# Version: 0.1.0 # +# Author: L. Paderi (@lpaderiAltravia) # +# Copyright: (c) 2025 Openapi®. All rights reserved. # +# License: MIT # +# Maintainer: Francesco Bianco # +# Contact: https://openapi.com/ # +# Repository: https://github.com/openapi/openapi-go-sdk/ # +# Documentation: https://console.openapi.com/ # +# # +# ═══════════════════════════════════════════════════════════════════════ # +# # +# "Truth lies at the source of the stream." # +# — English Proverb # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +## ========= +## Variables +## ========= + +VERSION := 0.2.1 +TAG := v$(VERSION) + +## ==================== +## Development Commands +## ==================== + +test: + @go test ./... + +vet: + @go vet ./... + +dev-push: + @git config credential.helper 'cache --timeout=3600' + @git add . + @git commit -m "$$(read -p 'Commit message: ' msg; echo $$msg)" || true + @git push + +## ================ +## Release Commands +## ================ + +push: + @git add . + @git commit -am "Updated at $$(date)" || true + @git push + +release: + @echo "==> Releasing $(TAG)..." + @if [ "$$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then \ + echo "ERROR: releases must be cut from the main branch"; exit 1; \ + fi + @if [ -n "$$(git status --porcelain)" ]; then \ + echo "ERROR: working directory is not clean"; exit 1; \ + fi + @echo "==> Running tests..." + @go test ./... + @echo "==> Running vet..." + @go vet ./... + @echo "==> Tagging $(TAG)..." + @git tag -a "$(TAG)" -m "Release $(TAG)" + @git push origin "$(TAG)" + @echo "==> Creating GitHub release..." + @gh release create "$(TAG)" \ + --title "$(TAG)" \ + --generate-notes \ + --verify-tag + @echo "==> Done. Release $(TAG) is live." \ No newline at end of file diff --git a/README.md b/README.md index 5aa74df..4018ca8 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,148 @@ +
+ + Openapi SDK for Go + -# OpenApi IT Go Client +

Openapi® client for Go

+

The perfect starting point to integrate Openapi® within your Go project

-This client is used to interact with the API found at [openapi.it](https://openapi.it/) +[![Build Status](https://github.com/openapi/openapi-go-sdk/actions/workflows/go.yml/badge.svg)](https://github.com/openapi/openapi-go-sdk/actions/workflows/go.yml) +[![Go Report Card](https://goreportcard.com/badge/github.com/openapi/openapi-go-sdk)](https://goreportcard.com/report/github.com/openapi/openapi-go-sdk) +[![Go Reference](https://pkg.go.dev/badge/github.com/openapi/openapi-go-sdk.svg)](https://pkg.go.dev/github.com/openapi/openapi-go-sdk) +[![License](https://img.shields.io/github/license/openapi/openapi-go-sdk)](LICENSE) +
+[![Linux Foundation Member](https://img.shields.io/badge/Linux%20Foundation-Silver%20Member-003778?logo=linux-foundation&logoColor=white)](https://www.linuxfoundation.org/about/members) +
+ +## Overview + +A minimal and agnostic Go SDK for Openapi, providing only the core HTTP primitives needed to interact with any Openapi service. ## Pre-requisites -Before using the OpenApi IT Go Client, you will need an account at [openapi.it](https://openapi.it/) and an API key to the sandbox and/or production environment +Before using the Openapi Go Client, you will need an account at [Openapi](https://console.openapi.com/) and an API key to the sandbox and/or production environment. + +## Features + +- **Agnostic Design**: No API-specific classes, works with any Openapi service +- **Minimal Dependencies**: Only requires Go 1.19+ +- **OAuth Support**: Built-in OAuth client for token management +- **HTTP Primitives**: GET, POST, PUT, DELETE and other HTTP methods +- **Clean Interface**: Simple and idiomatic Go API + +## What you can do + +With the Openapi Go Client, you can easily interact with a variety of services in the Openapi Marketplace. For example, you can: + +- 📩 **Send SMS messages** with delivery reports and custom sender IDs +- 💸 **Process bills and payments** in real time via API +- 🧾 **Send electronic invoices** securely to the Italian Revenue Agency +- 📄 **Generate PDFs** from HTML content, including JavaScript rendering +- ✉️ **Manage certified emails** and legal communications via Italian Legalmail + +For a complete list of all available services, check out the [Openapi Marketplace](https://console.openapi.com/) 🌐 ## Installation -You can install the OpenApi IT Go Client with the following command using go get: +Add the SDK to your project: ```bash -go get github.com/openapi-it/openapi-cli-go +go get github.com/openapi/openapi-go-sdk ``` - + +Then import the client package: + +```go +import "github.com/openapi/openapi-go-sdk/pkg/client" +``` + ## Usage +### Token generation + +Use `OauthClient` to authenticate with your credentials and generate a scoped access token. +The `test` flag switches between the sandbox (`true`) and production (`false`) OAuth endpoint. + ```go -// main.go package main import ( - client "github.com/openapi-it/openapi-cli-go/pkg/client" + "context" + "encoding/json" + "fmt" + "log" + + "github.com/openapi/openapi-go-sdk/pkg/client" ) func main() { - // Initialize the oauth client on the sandbox environment ctx := context.Background() + oauthClient := client.NewOauthClient("", "", true) - // Create a token for a list of scopes scopes := []string{ - "GET:test.imprese.openapi.it/advance", - "POST:test.postontarget.com/fields/country", + "GET:test.imprese.openapi.it/advance", + "POST:test.postontarget.com/fields/country", } - ttl := 3600 - resp, err := oauthClient.CreateToken(ctx, scopes, ttl) // returns the json as string + resp, err := oauthClient.CreateToken(ctx, scopes, 3600) if err != nil { log.Fatal(err) } - // The string response can be parsed into a custom object tokenResponse := struct { Scopes []string `json:"scopes"` Token string `json:"token"` }{} - _ = json.Unmarshal([]byte(resp), &tokenResponse) + if err := json.Unmarshal([]byte(resp), &tokenResponse); err != nil { + log.Fatal(err) + } + + fmt.Printf("token: %s\n", tokenResponse.Token) +} +``` + +### Making API calls + +Use `Client` with the token obtained above to call any Openapi service. +Pass the base URL and endpoint separately so the client can correctly attach query parameters. + +```go +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "log" + + "github.com/openapi/openapi-go-sdk/pkg/client" +) - // Initialize the client - client := client.NewClient(tokenResponse.Token) +func main() { + ctx := context.Background() + apiClient := client.NewClient("") - // Make a request with params + // GET with query parameters params := map[string]string{ "denominazione": "altravia", "provincia": "RM", "codice_ateco": "6201", } - _, err = client.Request( - ctx, - "GET", - "https://test.imprese.openapi.it", - "/advance", - nil, params, - ) + result, err := apiClient.Request(ctx, "GET", "https://test.imprese.openapi.it", "/advance", nil, params) if err != nil { log.Fatal(err) } + fmt.Println(result) - // Make a request with a payload + // POST with a JSON payload payload := struct { Limit int `json:"limit"` Query struct { CountryCode string `json:"country_code"` } `json:"query"` }{ - Limit: 0, + Limit: 10, Query: struct { CountryCode string `json:"country_code"` }{CountryCode: "IT"}, @@ -84,41 +151,79 @@ func main() { if err := json.NewEncoder(&buf).Encode(payload); err != nil { log.Fatal(err) } - _, err = client.Request( - ctx, - "POST", - "https://test.postontarget.com", - "/fields/country", - &buf, - nil, - ) - if err != nil { - log.Fatal(err) - } - - // Delete the token - _, err = oauthClient.DeleteToken(ctx, tokenResponse.Token) + result, err = apiClient.Request(ctx, "POST", "https://test.postontarget.com", "/fields/country", &buf, nil) if err != nil { log.Fatal(err) } + fmt.Println(result) } ``` +More complete examples are available in the [`examples/`](examples/) directory. + +## Testing + +The SDK ships with a suite of unit tests that use `net/http/httptest` — no real network calls, no credentials needed. + +Run the full suite: + +```bash +go test ./... +``` + +Run with verbose output to see each test case: + +```bash +go test -v ./pkg/client/... +``` + +Run a single test by name: + +```bash +go test -v -run TestCreateToken ./pkg/client/... +``` + ## Contributing -Contributions are always welcome! +Contributions are always welcome! Whether you want to report bugs, suggest new features, improve documentation, or contribute code, your help is appreciated. -See `contributing.md` for ways to get started. +See [docs/contributing.md](docs/contributing.md) for detailed instructions on how to get started. Please make sure to follow this project's [docs/code-of-conduct.md](docs/code-of-conduct.md) to help maintain a welcoming and collaborative environment. -Please adhere to this project's `code of conduct`. +## Authors +Meet the project authors: + +- Michael Cuffaro ([@maiku1008](https://www.github.com/maiku1008)) +- Openapi Team ([@openapi-it](https://github.com/openapi-it)) + +## Partners + +Meet our partners using Openapi or contributing to this SDK: + +- [Blank](https://www.blank.app/) +- [Credit Safe](https://www.creditsafe.com/) +- [Deliveroo](https://deliveroo.it/) +- [Gruppo MOL](https://molgroupitaly.it/it/) +- [Jakala](https://www.jakala.com/) +- [Octotelematics](https://www.octotelematics.com/) +- [OTOQI](https://otoqi.com/) +- [PWC](https://www.pwc.com/) +- [QOMODO S.R.L.](https://www.qomodo.me/) +- [SOUNDREEF S.P.A.](https://www.soundreef.com/) + +## Our Commitments + +We believe in open source and we act on that belief. We became Silver Members +of the Linux Foundation because we wanted to formally support the ecosystem +we build on every day. Open standards, open collaboration, and open governance +are part of how we work and how we think about software. ## License -[MIT](https://choosealicense.com/licenses/mit/) +This project is licensed under the [MIT License](LICENSE). +The MIT License is a permissive open-source license that allows you to freely use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the software, provided that the original copyright notice and this permission notice are included in all copies or substantial portions of the software. -## Authors +In short, you are free to use this SDK in your personal, academic, or commercial projects, with minimal restrictions. The project is provided "as-is", without any warranty of any kind, either expressed or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. -- [@maiku1008](https://www.github.com/maiku1008) -- [@openapi-it](https://github.com/openapi-it) +For more details, see the full license text at the [MIT License page](https://choosealicense.com/licenses/mit/). \ No newline at end of file diff --git a/docs/code-of-conduct.md b/docs/code-of-conduct.md new file mode 100644 index 0000000..4e71723 --- /dev/null +++ b/docs/code-of-conduct.md @@ -0,0 +1,30 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project a harassment-free experience for everyone. + +## Our Standards + +Examples of positive behavior: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy toward other community members + +Examples of unacceptable behavior: + +- Harassment, intimidation, or discrimination +- Public or private insults and derogatory comments +- Publishing others’ private information without consent +- Any other conduct reasonably considered inappropriate + +## Enforcement + +Instances of unacceptable behavior may be reported by contacting the project team at ``. All complaints will be reviewed promptly and fairly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1. \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..d3abc7b --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,45 @@ +# Contributing to Openapi SDK + +Thanks for considering contributing! 🎉 +We welcome all kinds of contributions: bug reports, feature requests, documentation improvements, and code enhancements. + +## How to Contribute + +1. **Fork the repository** and clone it locally: + ```bash + git clone https://github.com//.git + ``` + +2. **Create a branch** for your feature or fix: + ```bash + git checkout -b feature/your-feature-name + ``` + +3. **Make your changes** and commit them: + ```bash + git commit -m "Add some feature" + ``` + +4. **Push your branch** to your fork: + ```bash + git push origin feature/your-feature-name + ``` + +5. **Open a Pull Request** describing your changes. + +## Guidelines + +* Follow the existing **Php coding style**. +* Include **tests** for new features or bug fixes when applicable. +* Keep **commit messages clear and concise**. +* Update **documentation** as needed for your changes. + +## Reporting Issues + +To report bugs or request features, please **open an issue** on GitHub including: + +* Clear description of the problem or feature. +* Steps to reproduce (if applicable). +* Relevant logs or screenshots. + +Thank you for helping improve Openapi SDK! 🚀 \ No newline at end of file diff --git a/examples/api_calls/api_calls.go b/examples/api_calls/api_calls.go new file mode 100644 index 0000000..e68ee3b --- /dev/null +++ b/examples/api_calls/api_calls.go @@ -0,0 +1,51 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "log" + + "github.com/openapi/openapi-go-sdk/pkg/client" +) + +func main() { + ctx := context.Background() + + apiClient := client.NewClient("") + + // GET request with query parameters + params := map[string]string{ + "denominazione": "altravia", + "provincia": "RM", + "codice_ateco": "6201", + } + result, err := apiClient.Request(ctx, "GET", "https://test.imprese.openapi.it", "/advance", nil, params) + if err != nil { + log.Fatal(err) + } + fmt.Printf("GET API Response: %s\n", result) + + // POST request with payload + payload := struct { + Limit int `json:"limit"` + Query struct { + CountryCode string `json:"country_code"` + } `json:"query"` + }{ + Limit: 10, + Query: struct { + CountryCode string `json:"country_code"` + }{CountryCode: "IT"}, + } + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(payload); err != nil { + log.Fatal(err) + } + result, err = apiClient.Request(ctx, "POST", "https://test.postontarget.com", "/fields/country", &buf, nil) + if err != nil { + log.Fatal(err) + } + fmt.Printf("POST API Response: %s\n", result) +} \ No newline at end of file diff --git a/examples/token_generation/token_generation.go b/examples/token_generation/token_generation.go new file mode 100644 index 0000000..fc989a4 --- /dev/null +++ b/examples/token_generation/token_generation.go @@ -0,0 +1,39 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "github.com/openapi/openapi-go-sdk/pkg/client" +) + +func main() { + ctx := context.Background() + + // Initialize the OAuth client on the sandbox environment (test=true) + oauthClient := client.NewOauthClient("", "", true) + + scopes := []string{ + "GET:test.imprese.openapi.it/advance", + "POST:test.postontarget.com/fields/country", + } + ttl := 3600 + + resp, err := oauthClient.CreateToken(ctx, scopes, ttl) + if err != nil { + log.Fatal(err) + } + + tokenResponse := struct { + Scopes []string `json:"scopes"` + Token string `json:"token"` + }{} + if err := json.Unmarshal([]byte(resp), &tokenResponse); err != nil { + log.Fatal(err) + } + + fmt.Printf("Generated token: %s\n", tokenResponse.Token) + fmt.Println("Token created successfully!") +} \ No newline at end of file diff --git a/go.mod b/go.mod index a27ab13..7e126dd 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/openapi-it/openapi-cli-go +module github.com/openapi/openapi-go-sdk go 1.19 diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go new file mode 100644 index 0000000..3b134de --- /dev/null +++ b/pkg/client/client_test.go @@ -0,0 +1,55 @@ +package client + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestNewClient(t *testing.T) { + c := NewClient("test-token") + if c == nil { + t.Fatal("expected non-nil client") + } + if c.authHeader != "Bearer test-token" { + t.Errorf("expected auth header 'Bearer test-token', got %q", c.authHeader) + } +} + +func TestClientRequest(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != "Bearer test-token" { + t.Errorf("wrong authorization header: %q", r.Header.Get("Authorization")) + } + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status":"ok"}`)) + })) + defer server.Close() + + c := NewClient("test-token") + resp, err := c.Request(context.Background(), "GET", server.URL, "/test", nil, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(resp, "status") { + t.Errorf("unexpected response: %s", resp) + } +} + +func TestClientRequestWithParams(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Query().Get("key") != "value" { + t.Errorf("expected query param key=value, got %v", r.URL.Query()) + } + w.Write([]byte(`{"ok":true}`)) + })) + defer server.Close() + + c := NewClient("token") + _, err := c.Request(context.Background(), "GET", server.URL, "/test", nil, map[string]string{"key": "value"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} \ No newline at end of file diff --git a/pkg/client/oauthclient_test.go b/pkg/client/oauthclient_test.go new file mode 100644 index 0000000..18bf49a --- /dev/null +++ b/pkg/client/oauthclient_test.go @@ -0,0 +1,170 @@ +package client + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestNewOauthClient(t *testing.T) { + c := NewOauthClient("user", "apikey", false) + if c == nil { + t.Fatal("expected non-nil oauth client") + } + if c.baseURL != oauthBaseURL { + t.Errorf("expected base URL %q, got %q", oauthBaseURL, c.baseURL) + } + if !strings.HasPrefix(c.authHeader, "Basic ") { + t.Errorf("expected Basic auth header, got %q", c.authHeader) + } +} + +func TestNewOauthClientTest(t *testing.T) { + c := NewOauthClient("user", "apikey", true) + if c.baseURL != testOauthBaseURL { + t.Errorf("expected test base URL %q, got %q", testOauthBaseURL, c.baseURL) + } +} + +func TestGetScopes(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/scopes" { + t.Errorf("expected /scopes, got %q", r.URL.Path) + } + w.Write([]byte(`{"scopes":[]}`)) + })) + defer server.Close() + + c := &OauthClient{ + baseURL: server.URL, + authHeader: "Basic dXNlcjpha2V5", + httpClient: &http.Client{}, + } + resp, err := c.GetScopes(context.Background(), false) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(resp, "scopes") { + t.Errorf("unexpected response: %s", resp) + } +} + +func TestGetScopesWithLimit(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Query().Get("limit") != "1" { + t.Errorf("expected limit=1, got %q", r.URL.Query().Get("limit")) + } + w.Write([]byte(`{"scopes":[]}`)) + })) + defer server.Close() + + c := &OauthClient{ + baseURL: server.URL, + authHeader: "Basic dXNlcjpha2V5", + httpClient: &http.Client{}, + } + _, err := c.GetScopes(context.Background(), true) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestCreateToken(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Errorf("expected POST, got %s", r.Method) + } + if r.URL.Path != "/token" { + t.Errorf("expected /token, got %q", r.URL.Path) + } + w.Write([]byte(`{"token":"abc123","scopes":["GET:test.example.com"]}`)) + })) + defer server.Close() + + c := &OauthClient{ + baseURL: server.URL, + authHeader: "Basic dXNlcjpha2V5", + httpClient: &http.Client{}, + } + resp, err := c.CreateToken(context.Background(), []string{"GET:test.example.com"}, 3600) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(resp, "token") { + t.Errorf("unexpected response: %s", resp) + } +} + +func TestGetTokens(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Query().Get("scope") != "GET:test.example.com" { + t.Errorf("expected scope param, got %q", r.URL.Query().Get("scope")) + } + w.Write([]byte(`{"tokens":[]}`)) + })) + defer server.Close() + + c := &OauthClient{ + baseURL: server.URL, + authHeader: "Basic dXNlcjpha2V5", + httpClient: &http.Client{}, + } + resp, err := c.GetTokens(context.Background(), "GET:test.example.com") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(resp, "tokens") { + t.Errorf("unexpected response: %s", resp) + } +} + +func TestDeleteToken(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodDelete { + t.Errorf("expected DELETE, got %s", r.Method) + } + if !strings.HasSuffix(r.URL.Path, "/token-id") { + t.Errorf("expected path ending in /token-id, got %q", r.URL.Path) + } + w.Write([]byte(`{"deleted":true}`)) + })) + defer server.Close() + + c := &OauthClient{ + baseURL: server.URL, + authHeader: "Basic dXNlcjpha2V5", + httpClient: &http.Client{}, + } + resp, err := c.DeleteToken(context.Background(), "token-id") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(resp, "deleted") { + t.Errorf("unexpected response: %s", resp) + } +} + +func TestGetCounters(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/counters/daily/2024-01-01" { + t.Errorf("unexpected path: %q", r.URL.Path) + } + w.Write([]byte(`{"counters":[]}`)) + })) + defer server.Close() + + c := &OauthClient{ + baseURL: server.URL, + authHeader: "Basic dXNlcjpha2V5", + httpClient: &http.Client{}, + } + resp, err := c.GetCounters(context.Background(), "daily", "2024-01-01") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !strings.Contains(resp, "counters") { + t.Errorf("unexpected response: %s", resp) + } +} \ No newline at end of file