Add specs for swagger

This commit is contained in:
Eugene Burmakin 2024-11-26 20:18:08 +01:00
parent c5044781b9
commit 7b160bfe2d
8 changed files with 590 additions and 12 deletions

View file

@ -32,7 +32,6 @@ class Api::V1::PhotosController < ApiController
status: :ok
)
else
Rails.logger.error "Failed to fetch thumbnail: #{response.code} - #{response.body}"
render json: { error: 'Failed to fetch thumbnail' }, status: response.code
end
end

View file

@ -3,16 +3,13 @@
class Immich::ImportGeodata
attr_reader :user, :start_date, :end_date
def initialize(user, end_date:, start_date: '1970-01-01')
def initialize(user, start_date: '1970-01-01', end_date: nil)
@user = user
@start_date = start_date
@end_date = end_date
end
def call
raise ArgumentError, 'Immich API key is missing' if immich_api_key.blank?
raise ArgumentError, 'Immich URL is missing' if user.settings['immich_url'].blank?
immich_data = retrieve_immich_data
log_no_data and return if immich_data.empty?

View file

@ -12,6 +12,9 @@ class Immich::RequestPhotos
end
def call
raise ArgumentError, 'Immich API key is missing' if immich_api_key.blank?
raise ArgumentError, 'Immich URL is missing' if user.settings['immich_url'].blank?
data = retrieve_immich_data
time_framed_data(data)

View file

@ -79,7 +79,7 @@ Rails.application.routes.draw do
resources :borders, only: :index
end
resources :photos do
resources :photos, only: %i[index] do
member do
get 'thumbnail', constraints: { id: %r{[^/]+} }
end

View file

@ -22,5 +22,14 @@ FactoryBot.define do
trait :admin do
admin { true }
end
trait :with_immich_credentials do
settings do
{
immich_url: 'https://immich.example.com',
immich_api_key: '1234567890'
}
end
end
end
end

View file

@ -1,11 +1,44 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe "Api::V1::Photos", type: :request do
describe "GET /index" do
it "returns http success" do
get "/api/v1/photos/index"
expect(response).to have_http_status(:success)
RSpec.describe 'Api::V1::Photos', type: :request do
describe 'GET /index' do
let(:user) { create(:user) }
let(:photo_data) do
[
{
'id' => '123',
'latitude' => 35.6762,
'longitude' => 139.6503,
'createdAt' => '2024-01-01T00:00:00.000Z',
'type' => 'photo'
},
{
'id' => '456',
'latitude' => 40.7128,
'longitude' => -74.0060,
'createdAt' => '2024-01-02T00:00:00.000Z',
'type' => 'photo'
}
]
end
context 'when the request is successful' do
before do
allow_any_instance_of(Immich::RequestPhotos).to receive(:call).and_return(photo_data)
get '/api/v1/photos', params: { api_key: user.api_key }
end
it 'returns http success' do
expect(response).to have_http_status(:success)
end
it 'returns photos data as JSON' do
expect(JSON.parse(response.body)).to eq(photo_data)
end
end
end
end

View file

@ -0,0 +1,249 @@
# frozen_string_literal: true
require 'swagger_helper'
RSpec.describe 'Api::V1::PhotosController', type: :request do
let(:user) { create(:user, :with_immich_credentials) }
let(:api_key) { user.api_key }
let(:start_date) { '2024-01-01' }
let(:end_date) { '2024-01-02' }
let!(:immich_image) do
{
"id": '7fe486e3-c3ba-4b54-bbf9-1281b39ed15c',
"deviceAssetId": 'IMG_9913.jpeg-1168914',
"ownerId": 'f579f328-c355-438c-a82c-fe3390bd5f08',
"deviceId": 'CLI',
"libraryId": nil,
"type": 'IMAGE',
"originalPath": 'upload/library/admin/2023/2023-06-08/IMG_9913.jpeg',
"originalFileName": 'IMG_9913.jpeg',
"originalMimeType": 'image/jpeg',
"thumbhash": '4RgONQaZqYaH93g3h3p3d6RfPPrG',
"fileCreatedAt": '2023-06-08T07:58:45.637Z',
"fileModifiedAt": '2023-06-08T09:58:45.000Z',
"localDateTime": '2024-01-01T09:58:45.637Z',
"updatedAt": '2024-08-24T18:20:47.965Z',
"isFavorite": false,
"isArchived": false,
"isTrashed": false,
"duration": '0:00:00.00000',
"exifInfo": {
"make": 'Apple',
"model": 'iPhone 12 Pro',
"exifImageWidth": 4032,
"exifImageHeight": 3024,
"fileSizeInByte": 1_168_914,
"orientation": '6',
"dateTimeOriginal": '2023-06-08T07:58:45.637Z',
"modifyDate": '2023-06-08T07:58:45.000Z',
"timeZone": 'Europe/Berlin',
"lensModel": 'iPhone 12 Pro back triple camera 4.2mm f/1.6',
"fNumber": 1.6,
"focalLength": 4.2,
"iso": 320,
"exposureTime": '1/60',
"latitude": 52.11,
"longitude": 13.22,
"city": 'Johannisthal',
"state": 'Berlin',
"country": 'Germany',
"description": '',
"projectionType": nil,
"rating": nil
},
"livePhotoVideoId": nil,
"people": [],
"checksum": 'aL1edPVg4ZpEnS6xCRWNUY0pUS8=',
"isOffline": false,
"hasMetadata": true,
"duplicateId": '88a34bee-783d-46e4-aa52-33b75ffda375',
"resized": true
}
end
let(:immich_data) do
{
"albums": {
"total": 0,
"count": 0,
"items": [],
"facets": []
},
"assets": {
"total": 1000,
"count": 1000,
"items": [immich_image]
}
}.to_json
end
before do
stub_request(:post, "#{user.settings['immich_url']}/api/search/metadata")
.to_return(status: 200, body: immich_data)
stub_request(:get, "#{user.settings['immich_url']}/api/assets/7fe486e3-c3ba-4b54-bbf9-1281b39ed15c/thumbnail?size=preview")
.to_return(status: 200, body: immich_image.to_json, headers: {})
stub_request(:get, "#{user.settings['immich_url']}/api/assets/nonexistent/thumbnail?size=preview")
.to_return(status: 404, body: [].to_json, headers: {})
end
path '/api/v1/photos' do
get 'Lists photos' do
tags 'Photos'
produces 'application/json'
parameter name: :api_key, in: :query, type: :string, required: true
parameter name: :start_date, in: :query, type: :string, required: true,
description: 'Start date in ISO8601 format, e.g. 2024-01-01'
parameter name: :end_date, in: :query, type: :string, required: true,
description: 'End date in ISO8601 format, e.g. 2024-01-02'
response '200', 'photos found' do
schema type: :array,
items: {
type: :object,
properties: {
id: { type: :string },
deviceAssetId: { type: :string },
ownerId: { type: :string },
type: { type: :string },
originalPath: { type: :string },
originalFileName: { type: :string },
originalMimeType: { type: :string },
thumbhash: { type: :string },
fileCreatedAt: { type: :string, format: 'date-time' },
fileModifiedAt: { type: :string, format: 'date-time' },
localDateTime: { type: :string, format: 'date-time' },
updatedAt: { type: :string, format: 'date-time' },
isFavorite: { type: :boolean },
isArchived: { type: :boolean },
isTrashed: { type: :boolean },
duration: { type: :string },
exifInfo: {
type: :object,
properties: {
make: { type: :string },
model: { type: :string },
exifImageWidth: { type: :integer },
exifImageHeight: { type: :integer },
fileSizeInByte: { type: :integer },
orientation: { type: :string },
dateTimeOriginal: { type: :string, format: 'date-time' },
modifyDate: { type: :string, format: 'date-time' },
timeZone: { type: :string },
lensModel: { type: :string },
fNumber: { type: :number, format: :float },
focalLength: { type: :number, format: :float },
iso: { type: :integer },
exposureTime: { type: :string },
latitude: { type: :number, format: :float },
longitude: { type: :number, format: :float },
city: { type: :string },
state: { type: :string },
country: { type: :string },
description: { type: :string },
projectionType: { type: %i[string null] },
rating: { type: %i[integer null] }
}
},
checksum: { type: :string },
isOffline: { type: :boolean },
hasMetadata: { type: :boolean },
duplicateId: { type: :string },
resized: { type: :boolean }
},
required: %w[id deviceAssetId ownerId type originalPath
originalFileName originalMimeType thumbhash
fileCreatedAt fileModifiedAt localDateTime
updatedAt isFavorite isArchived isTrashed duration
exifInfo checksum isOffline hasMetadata duplicateId resized]
}
run_test! do |response|
data = JSON.parse(response.body)
expect(data).to be_an(Array)
end
end
end
end
path '/api/v1/photos/{id}/thumbnail' do
get 'Retrieves a photo' do
tags 'Photos'
produces 'application/json'
parameter name: :id, in: :path, type: :string, required: true
parameter name: :api_key, in: :query, type: :string, required: true
response '200', 'photo found' do
schema type: :object,
properties: {
id: { type: :string },
deviceAssetId: { type: :string },
ownerId: { type: :string },
type: { type: :string },
originalPath: { type: :string },
originalFileName: { type: :string },
originalMimeType: { type: :string },
thumbhash: { type: :string },
fileCreatedAt: { type: :string, format: 'date-time' },
fileModifiedAt: { type: :string, format: 'date-time' },
localDateTime: { type: :string, format: 'date-time' },
updatedAt: { type: :string, format: 'date-time' },
isFavorite: { type: :boolean },
isArchived: { type: :boolean },
isTrashed: { type: :boolean },
duration: { type: :string },
exifInfo: {
type: :object,
properties: {
make: { type: :string },
model: { type: :string },
exifImageWidth: { type: :integer },
exifImageHeight: { type: :integer },
fileSizeInByte: { type: :integer },
orientation: { type: :string },
dateTimeOriginal: { type: :string, format: 'date-time' },
modifyDate: { type: :string, format: 'date-time' },
timeZone: { type: :string },
lensModel: { type: :string },
fNumber: { type: :number, format: :float },
focalLength: { type: :number, format: :float },
iso: { type: :integer },
exposureTime: { type: :string },
latitude: { type: :number, format: :float },
longitude: { type: :number, format: :float },
city: { type: :string },
state: { type: :string },
country: { type: :string },
description: { type: :string },
projectionType: { type: %i[string null] },
rating: { type: %i[integer null] }
}
},
checksum: { type: :string },
isOffline: { type: :boolean },
hasMetadata: { type: :boolean },
duplicateId: { type: :string },
resized: { type: :boolean }
}
let(:id) { '7fe486e3-c3ba-4b54-bbf9-1281b39ed15c' }
run_test! do |response|
data = JSON.parse(response.body)
expect(data).to be_a(Hash)
expect(data['id']).to eq(id)
end
end
response '404', 'photo not found' do
let(:id) { 'nonexistent' }
let(:api_key) { user.api_key }
run_test! do |response|
data = JSON.parse(response.body)
expect(data['error']).to eq('Failed to fetch thumbnail')
end
end
end
end
end

View file

@ -312,6 +312,294 @@ paths:
isorcv: '2024-02-03T13:00:03Z'
isotst: '2024-02-03T13:00:03Z'
disptst: '2024-02-03 13:00:03'
"/api/v1/photos":
get:
summary: Lists photos
tags:
- Photos
parameters:
- name: api_key
in: query
required: true
schema:
type: string
- name: start_date
in: query
required: true
description: Start date in ISO8601 format, e.g. 2024-01-01
schema:
type: string
- name: end_date
in: query
required: true
description: End date in ISO8601 format, e.g. 2024-01-02
schema:
type: string
responses:
'200':
description: photos found
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: string
deviceAssetId:
type: string
ownerId:
type: string
type:
type: string
originalPath:
type: string
originalFileName:
type: string
originalMimeType:
type: string
thumbhash:
type: string
fileCreatedAt:
type: string
format: date-time
fileModifiedAt:
type: string
format: date-time
localDateTime:
type: string
format: date-time
updatedAt:
type: string
format: date-time
isFavorite:
type: boolean
isArchived:
type: boolean
isTrashed:
type: boolean
duration:
type: string
exifInfo:
type: object
properties:
make:
type: string
model:
type: string
exifImageWidth:
type: integer
exifImageHeight:
type: integer
fileSizeInByte:
type: integer
orientation:
type: string
dateTimeOriginal:
type: string
format: date-time
modifyDate:
type: string
format: date-time
timeZone:
type: string
lensModel:
type: string
fNumber:
type: number
format: float
focalLength:
type: number
format: float
iso:
type: integer
exposureTime:
type: string
latitude:
type: number
format: float
longitude:
type: number
format: float
city:
type: string
state:
type: string
country:
type: string
description:
type: string
projectionType:
type:
- string
- 'null'
rating:
type:
- integer
- 'null'
checksum:
type: string
isOffline:
type: boolean
hasMetadata:
type: boolean
duplicateId:
type: string
resized:
type: boolean
required:
- id
- deviceAssetId
- ownerId
- type
- originalPath
- originalFileName
- originalMimeType
- thumbhash
- fileCreatedAt
- fileModifiedAt
- localDateTime
- updatedAt
- isFavorite
- isArchived
- isTrashed
- duration
- exifInfo
- checksum
- isOffline
- hasMetadata
- duplicateId
- resized
"/api/v1/photos/{id}/thumbnail":
get:
summary: Retrieves a photo
tags:
- Photos
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: api_key
in: query
required: true
schema:
type: string
responses:
'200':
description: photo found
content:
application/json:
schema:
type: object
properties:
id:
type: string
deviceAssetId:
type: string
ownerId:
type: string
type:
type: string
originalPath:
type: string
originalFileName:
type: string
originalMimeType:
type: string
thumbhash:
type: string
fileCreatedAt:
type: string
format: date-time
fileModifiedAt:
type: string
format: date-time
localDateTime:
type: string
format: date-time
updatedAt:
type: string
format: date-time
isFavorite:
type: boolean
isArchived:
type: boolean
isTrashed:
type: boolean
duration:
type: string
exifInfo:
type: object
properties:
make:
type: string
model:
type: string
exifImageWidth:
type: integer
exifImageHeight:
type: integer
fileSizeInByte:
type: integer
orientation:
type: string
dateTimeOriginal:
type: string
format: date-time
modifyDate:
type: string
format: date-time
timeZone:
type: string
lensModel:
type: string
fNumber:
type: number
format: float
focalLength:
type: number
format: float
iso:
type: integer
exposureTime:
type: string
latitude:
type: number
format: float
longitude:
type: number
format: float
city:
type: string
state:
type: string
country:
type: string
description:
type: string
projectionType:
type:
- string
- 'null'
rating:
type:
- integer
- 'null'
checksum:
type: string
isOffline:
type: boolean
hasMetadata:
type: boolean
duplicateId:
type: string
resized:
type: boolean
'404':
description: photo not found
"/api/v1/points":
get:
summary: Retrieves all points