Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
284696e
build: start the 2.0 line on Ruby 3.4
mantas Aug 27, 2026
bb0b771
refactor!: rewrite the client internals
mantas Aug 27, 2026
b4125cd
feat: add RBS signatures and Steep type checking
mantas Aug 27, 2026
0d8c059
ci: check types and specs on a Ruby matrix, release via trusted publi…
mantas Aug 27, 2026
db0083b
docs: document the 2.0 API and the upgrade path
mantas Aug 27, 2026
d776673
feat: pattern matching, derived clients and pattern-matched shape checks
mantas Aug 27, 2026
9b116f1
ci: boot Zammad and drive it with this gem end to end
mantas Aug 27, 2026
fb6c683
ci: fix the Zammad boot and unblock the Ruby head job
mantas Aug 27, 2026
229948b
fix: detect an already-configured Zammad by authenticating
mantas Aug 27, 2026
9a6adc5
ci: drop Ruby head from the matrix
mantas Aug 27, 2026
d8466bf
ci: test only released Rubies
mantas Aug 27, 2026
8401f89
ci: test on Ruby 4.0 as well
mantas Aug 27, 2026
907e94e
build: add Mantas Masalskis as a gem author
mantas Aug 27, 2026
e01212e
docs: add runnable examples for the 2.0 API
mantas Aug 27, 2026
b42c11a
docs: add a pagination example
mantas Aug 27, 2026
204b7e3
docs: add an example of driving pagination in batches by hand
mantas Aug 27, 2026
2d60cb1
fix: decode the reload response before replacing attributes
mantas Sep 10, 2026
48e3a8e
fix: close two gaps in the credential-redaction contract
mantas Sep 10, 2026
8ce5029
fix: keep the original value as the baseline in changes
mantas Sep 10, 2026
4ee31b2
test: make two vacuous examples able to fail
mantas Sep 10, 2026
dedfdd2
docs: harden the examples against hostile data
mantas Sep 10, 2026
8a2e421
feat!: chain collection pagination instead of passing keywords
mantas Sep 10, 2026
aa58220
docs: correct the documented type of ResponseError#response
mantas Sep 10, 2026
ef4c354
feat: reach any endpoint with client.get/post/put/delete
mantas Sep 10, 2026
4f0e41c
feat!: return false from save on a validation failure
mantas Sep 10, 2026
2cd4509
fix!: stop handing out the mutable attribute hash
mantas Sep 10, 2026
65a780d
feat: open the Faraday stack with adapter and middleware
mantas Sep 10, 2026
8ed7ac4
feat: assign and save a hash of attributes in one call
mantas Sep 10, 2026
7e3e3f0
feat: add find_by, exists? and pluck
mantas Sep 10, 2026
1c282a6
feat: add Client.from_env, #me and #version
mantas Sep 10, 2026
d0c2d12
feat: reach related records through record.related
mantas Sep 10, 2026
cfc9ffd
feat: enumerate a resource proxy without naming all
mantas Sep 10, 2026
ce08783
feat: ship a test kit for code that calls this client
mantas Sep 10, 2026
755a285
test: assert the raising save in the integration specs
mantas Sep 10, 2026
91e60a6
docs: record this round's changes in the upgrade path
mantas Sep 10, 2026
359574a
feat: compare records by the Zammad record they came from
mantas Sep 10, 2026
a389969
feat: serialize a record as its attributes
mantas Sep 10, 2026
db45a04
feat: ask a collection for its size and whether it is empty
mantas Sep 10, 2026
205a941
docs: document how records compare and serialize
mantas Sep 10, 2026
e1cf29d
docs: document the collection size and empty? readers
mantas Sep 10, 2026
70db1f0
feat!: size the call that reads instead of chaining per
mantas Sep 10, 2026
63e00eb
docs: rewrite the examples around the client's defaults
mantas Sep 11, 2026
bd0a532
docs: ask for feedback on 2.0 while it can still change
mantas Sep 11, 2026
33400fb
fix: reject a logger that cannot log
mantas Sep 11, 2026
b66f5ea
docs: sort the migration guide by what it costs to miss
mantas Sep 11, 2026
d7eabdb
fix: stop reporting a working paginator as stuck
mantas Sep 11, 2026
bdc4ce5
fix: raise on a nil query value instead of dropping it
mantas Sep 11, 2026
f30116b
fix: record test-kit queries the way the transport sends them
mantas Sep 11, 2026
c3a84a4
refactor: replace a record's state in one place
mantas Sep 11, 2026
de960a4
fix: clear the previous error before a save attempt
mantas Sep 11, 2026
a22a373
feat: tell a destroyed record apart from a live one
mantas Sep 11, 2026
5fbd704
fix: stop claiming a writer that read-only records refuse
mantas Sep 11, 2026
c635e9f
refactor: record whether a response was JSON
mantas Sep 11, 2026
dbb9136
perf: build the test kit's client once
mantas Sep 11, 2026
fc6fddf
perf: download the smoke attachment once
mantas Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@ updates:
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns: ["*"]
- package-ecosystem: "bundler"
directory: "/"
schedule:
interval: "weekly"
groups:
# Lint and type tooling churns often and never affects the shipped gem.
development:
dependency-type: "development"
patterns: ["*"]
runtime:
dependency-type: "production"
patterns: ["*"]
168 changes: 147 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,90 @@
name: CI

on:
push:
branches: [master]
pull_request:
schedule:
# Run every on Friday to ensure everything works as expected.
- cron: '0 6 * * 5'
# Weekly, to catch breakage from new Ruby or Zammad releases.
- cron: '0 6 * * 5'
workflow_dispatch:
inputs:
zammad_ref:
description: 'Zammad git ref to run the integration specs against'
default: 'develop'
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
lint:
name: RuboCop
runs-on: ubuntu-latest
container:
image: zammad/zammad-ci:latest
steps:
- uses: actions/checkout@v7
- name: Run lint actions
shell: bash
run: |
source /etc/profile.d/rvm.sh # ensure RVM is loaded
bundle update --bundler
bundle install -j $(nproc)
bundle exec rubocop
test:
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- run: bundle exec rubocop --format github

types:
name: Steep
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- run: bundle exec steep check

unit:
name: Unit specs (Ruby ${{ matrix.ruby }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Stable releases only, from required_ruby_version up to the current
# stable line. There is no 3.5: that line was abandoned after
# 3.5.0-preview1 and became 4.0.
#
# 'head' is absent because it cannot install at all, for two reasons
# outside this gem. With Gemfile.lock present, bundler honours
# `BUNDLED WITH 2.6.9`, self-downgrades from head's 4.1.0.dev and dies
# with NameError on the removed Pathname::SEPARATOR_PAT. Without the
# lockfile, a fresh resolution pulls steep -> listen -> rb-inotify ->
# ffi, which requires Ruby < 4.1.dev.
ruby: ['3.4', '4.0']
env:
# The unit specs do not need the type-checking toolchain, and skipping it
# keeps this job fast. The `types` job installs it separately.
BUNDLE_WITHOUT: development
steps:
- uses: actions/checkout@v7
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run unit specs
run: bundle exec rake spec:unit
env:
COVERAGE: 'true'

integration:
name: Integration specs (live Zammad)
runs-on: ubuntu-latest
# Booting Zammad costs far more runner time than the unit suite, so there
# is no point paying for it when the unit specs are already failing.
needs: unit
# A full Zammad boot takes many minutes; without a cap a hung boot would
# occupy a runner until the six hour default expires.
timeout-minutes: 45
container:
image: zammad/zammad-ci:latest
services:
Expand All @@ -32,26 +95,89 @@ jobs:
POSTGRES_PASSWORD: zammad
redis:
image: redis:7
env:
ZAMMAD_REF: ${{ inputs.zammad_ref || 'develop' }}
TEST_USER: admin@example.com
TEST_PASSWORD: test
steps:
- uses: actions/checkout@v7
- name: Set up Zammad

- name: Report the toolchain
shell: bash
run: |
git clone --depth 1 https://github.com/zammad/zammad.git
source /etc/profile.d/rvm.sh
# The gem requires Ruby >= 3.4; fail here with a clear message rather
# than inside a confusing bundler resolution error.
ruby -v
ruby -e 'abort "zammad-ci image ships Ruby #{RUBY_VERSION}, this gem needs >= 3.4" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.4")'

- name: Boot Zammad
shell: bash
run: |
# No `set -u`: /etc/profile.d/rvm.sh reads unset variables and aborts
# under nounset.
set -eo pipefail
git clone --depth 1 --branch "$ZAMMAD_REF" https://github.com/zammad/zammad.git
cd zammad
source /etc/profile.d/rvm.sh # ensure RVM is loaded
source /etc/profile.d/rvm.sh
bundle config set --local frozen 'true'
bundle config set --local path 'vendor'
bundle install -j $(nproc)
bundle install -j "$(nproc)"
bundle exec ruby .gitlab/configure_environment.rb
# Each workflow step runs in its own shell, so Zammad's generated
# environment has to be promoted to the job environment to survive.
sed -E 's/^export +//' .gitlab/environment.env \
| grep -E '^[A-Za-z_][A-Za-z0-9_]*=' >> "$GITHUB_ENV"
source .gitlab/environment.env
RAILS_ENV=test bundle exec rake db:create
cp contrib/auto_wizard_test.json auto_wizard.json
bundle exec rake zammad:ci:test:start
- name: Run Ruby API integration tests
echo "TEST_URL=http://localhost:${RAILS_PORT:-3000}/" >> "$GITHUB_ENV"

- name: Wait for Zammad to answer
shell: bash
run: |
probe="${TEST_URL%/}/api/v1/getting_started"
for attempt in $(seq 1 60); do
if curl -sSf --max-time 5 "$probe" >/dev/null 2>&1; then
echo "Zammad answered at $TEST_URL after ${attempt} attempt(s)"
exit 0
fi
sleep 5
done
echo "::error::Zammad never answered at $probe"
exit 1

- name: Install the gem's dependencies
shell: bash
run: |
source /etc/profile.d/rvm.sh
bundle install -j "$(nproc)"

- name: Drive Zammad with this gem
shell: bash
run: |
source /etc/profile.d/rvm.sh
bundle exec ruby script/check_connection.rb

- name: Run the integration specs
shell: bash
run: |
source /etc/profile.d/rvm.sh
bundle exec rake spec:integration

- name: Collect Zammad logs on failure
if: failure()
shell: bash
run: |
source /etc/profile.d/rvm.sh # ensure RVM is loaded
bundle update --bundler
bundle install -j $(nproc)
bundle exec rspec
echo '--- zammad/log ---'
tail -n 200 zammad/log/*.log 2>/dev/null || echo 'no logs found'

- name: Upload Zammad logs
if: failure()
uses: actions/upload-artifact@v5
with:
name: zammad-logs
path: zammad/log/
if-no-files-found: ignore
retention-days: 7
29 changes: 29 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Release

# Publishes to RubyGems via trusted publishing (OIDC), so no API key is
# stored in this repository. Configure the trusted publisher once at
# https://rubygems.org/gems/zammad_api/trusted_publishers
on:
push:
tags: ['v*']

permissions:
contents: read

jobs:
release:
name: Build and publish
runs-on: ubuntu-latest
environment: rubygems
permissions:
contents: write # create the GitHub release and push the tag commit
id-token: write # request the OIDC token for trusted publishing
steps:
- uses: actions/checkout@v7
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- name: Verify the release candidate
run: bundle exec rake spec:unit rubocop steep
- uses: rubygems/release-gem@v1
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/.bundle/
/.ruby-version
/.yardoc
/.steep/
/.yardoc/
/_yardoc/
/coverage/
/doc/
Expand Down
50 changes: 20 additions & 30 deletions .overcommit.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
# Use this file to configure the Overcommit hooks you wish to use. This will
# extend the default configuration defined in:
# https://github.com/sds/overcommit/blob/master/config/default.yml
# See https://github.com/sds/overcommit#configuration
#
# At the topmost level of this YAML file is a key representing type of hook
# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
# customize each hook, such as whether to only run it on certain files (via
# `include`), whether to only display output if it fails (via `quiet`), etc.
#
# For a complete list of hooks, see:
# https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
#
# For a complete list of options that you can use to customize hooks, see:
# https://github.com/sds/overcommit#configuration
#
# Uncomment the following lines to make the configuration take effect.
# Install with: bundle exec overcommit --install

PreCommit:
RuboCop:
enabled: true
on_warn: fail # Treat all warnings as failures
#
# TrailingWhitespace:
# enabled: true
# exclude:
# - '**/db/structure.sql' # Ignore trailing whitespace in generated files
#
#PostCheckout:
# ALL: # Special hook name that customizes all hooks of this type
# quiet: true # Change all post-checkout hooks to only display output on failure
#
# IndexTags:
# enabled: true # Generate a tags file with `ctags` each time HEAD changes
RuboCop:
enabled: true
on_warn: fail # Treat all warnings as failures
command: ['bundle', 'exec', 'rubocop']

TrailingWhitespace:
enabled: true

YamlSyntax:
enabled: true

BundleCheck:
enabled: true

RSpec:
enabled: true
description: 'Run the unit specs'
command: ['bundle', 'exec', 'rspec', 'spec/unit']
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
--require spec_helper
--format documentation
--color
Loading