mirror of https://github.com/artifacthub/hub.git
Add endpoint for Harbor replication adapter (#1001)
Closes #997 Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com>
This commit is contained in:
parent
3ce3e4c091
commit
617105b00c
|
|
@ -272,6 +272,15 @@ func (h *Handlers) setupRouter() {
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
r.With(h.Users.RequireLogin).Post("/images", h.Static.SaveImage)
|
r.With(h.Users.RequireLogin).Post("/images", h.Static.SaveImage)
|
||||||
|
|
||||||
|
// Harbor replication
|
||||||
|
//
|
||||||
|
// This endpoint is used by the Harbor replication Artifact Hub adapter.
|
||||||
|
// It returns some information about all packages versions of Helm kind
|
||||||
|
// available so that they can be synchronized in Harbor deployments. It
|
||||||
|
// will probably start being used in Harbor 2.2.0, so we need to be
|
||||||
|
// careful to not introduce breaking changes.
|
||||||
|
r.Get("/harborReplication", h.Packages.GetHarborReplicationDump)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Monocular compatible search API
|
// Monocular compatible search API
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,19 @@ func (h *Handlers) GetChangeLog(w http.ResponseWriter, r *http.Request) {
|
||||||
helpers.RenderJSON(w, dataJSON, helpers.DefaultAPICacheMaxAge, http.StatusOK)
|
helpers.RenderJSON(w, dataJSON, helpers.DefaultAPICacheMaxAge, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHarborReplicationDump is an http handler used to get a summary of all
|
||||||
|
// available packages versions of kind Helm in the hub database so that they
|
||||||
|
// can be synchronized in Harbor.
|
||||||
|
func (h *Handlers) GetHarborReplicationDump(w http.ResponseWriter, r *http.Request) {
|
||||||
|
dataJSON, err := h.pkgManager.GetHarborReplicationDumpJSON(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error().Err(err).Str("method", "GetHarborReplicationDump").Send()
|
||||||
|
helpers.RenderErrorJSON(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helpers.RenderJSON(w, dataJSON, 1*time.Hour, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
// GetRandom is an http handler used to get some random packages from the hub
|
// GetRandom is an http handler used to get some random packages from the hub
|
||||||
// database.
|
// database.
|
||||||
func (h *Handlers) GetRandom(w http.ResponseWriter, r *http.Request) {
|
func (h *Handlers) GetRandom(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,43 @@ func TestGetChangeLog(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetHarborReplicationDump(t *testing.T) {
|
||||||
|
t.Run("get harbor replication dump succeeded", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
|
hw := newHandlersWrapper()
|
||||||
|
hw.pm.On("GetHarborReplicationDumpJSON", r.Context()).Return([]byte("dataJSON"), nil)
|
||||||
|
hw.h.GetHarborReplicationDump(w, r)
|
||||||
|
resp := w.Result()
|
||||||
|
defer resp.Body.Close()
|
||||||
|
h := resp.Header
|
||||||
|
data, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
assert.Equal(t, "application/json", h.Get("Content-Type"))
|
||||||
|
assert.Equal(t, helpers.BuildCacheControlHeader(1*time.Hour), h.Get("Cache-Control"))
|
||||||
|
assert.Equal(t, []byte("dataJSON"), data)
|
||||||
|
hw.pm.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error getting harbor replication dump", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
|
hw := newHandlersWrapper()
|
||||||
|
hw.pm.On("GetHarborReplicationDumpJSON", r.Context()).Return(nil, tests.ErrFakeDB)
|
||||||
|
hw.h.GetHarborReplicationDump(w, r)
|
||||||
|
resp := w.Result()
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
||||||
|
hw.pm.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetRandom(t *testing.T) {
|
func TestGetRandom(t *testing.T) {
|
||||||
t.Run("get random packages succeeded", func(t *testing.T) {
|
t.Run("get random packages succeeded", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
{{ template "organizations/user_belongs_to_organization.sql" }}
|
{{ template "organizations/user_belongs_to_organization.sql" }}
|
||||||
|
|
||||||
{{ template "packages/generate_package_tsdoc.sql" }}
|
{{ template "packages/generate_package_tsdoc.sql" }}
|
||||||
|
{{ template "packages/get_harbor_replication_dump.sql" }}
|
||||||
{{ template "packages/get_package.sql" }}
|
{{ template "packages/get_package.sql" }}
|
||||||
{{ template "packages/get_package_changelog.sql" }}
|
{{ template "packages/get_package_changelog.sql" }}
|
||||||
{{ template "packages/get_package_summary.sql" }}
|
{{ template "packages/get_package_summary.sql" }}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
-- get_harbor_replication_dump returns a json list with all packages versions
|
||||||
|
-- of kind Helm available so that they can be synchronized in Harbor.
|
||||||
|
create or replace function get_harbor_replication_dump()
|
||||||
|
returns setof json as $$
|
||||||
|
select coalesce(json_agg(json_build_object(
|
||||||
|
'repository', r.name,
|
||||||
|
'package', p.normalized_name,
|
||||||
|
'version', s.version,
|
||||||
|
'url', s.content_url
|
||||||
|
)), '[]')
|
||||||
|
from package p
|
||||||
|
join repository r using (repository_id)
|
||||||
|
join snapshot s using (package_id)
|
||||||
|
where r.repository_kind_id = 0
|
||||||
|
and (s.deprecated is null or s.deprecated = false)
|
||||||
|
and s.content_url is not null;
|
||||||
|
$$ language sql;
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
-- Start transaction and plan tests
|
||||||
|
begin;
|
||||||
|
select plan(2);
|
||||||
|
|
||||||
|
-- Declare some variables
|
||||||
|
\set org1ID '00000000-0000-0000-0000-000000000001'
|
||||||
|
\set repo1ID '00000000-0000-0000-0000-000000000001'
|
||||||
|
\set repo2ID '00000000-0000-0000-0000-000000000002'
|
||||||
|
\set repo3ID '00000000-0000-0000-0000-000000000003'
|
||||||
|
\set package1ID '00000000-0000-0000-0000-000000000001'
|
||||||
|
\set package2ID '00000000-0000-0000-0000-000000000002'
|
||||||
|
\set package3ID '00000000-0000-0000-0000-000000000003'
|
||||||
|
\set package4ID '00000000-0000-0000-0000-000000000004'
|
||||||
|
|
||||||
|
-- No packages at this point
|
||||||
|
select is(
|
||||||
|
get_harbor_replication_dump()::jsonb,
|
||||||
|
'[]'::jsonb,
|
||||||
|
'No packages in db yet, empty dump expected'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Seed some data
|
||||||
|
insert into organization (organization_id, name, display_name, description, home_url)
|
||||||
|
values (:'org1ID', 'org1', 'Organization 1', 'Description 1', 'https://org1.com');
|
||||||
|
insert into repository (repository_id, name, display_name, url, repository_kind_id, organization_id)
|
||||||
|
values (:'repo1ID', 'repo1', 'Repo 1', 'https://repo1.com', 0, :'org1ID');
|
||||||
|
insert into repository (repository_id, name, display_name, url, repository_kind_id, organization_id)
|
||||||
|
values (:'repo2ID', 'repo2', 'Repo 2', 'https://repo2.com', 0, :'org1ID');
|
||||||
|
insert into repository (repository_id, name, display_name, url, repository_kind_id, organization_id)
|
||||||
|
values (:'repo3ID', 'repo3', 'Repo 3', 'https://repo3.com', 1, :'org1ID');
|
||||||
|
insert into package (
|
||||||
|
package_id,
|
||||||
|
name,
|
||||||
|
latest_version,
|
||||||
|
repository_id
|
||||||
|
) values (
|
||||||
|
:'package1ID',
|
||||||
|
'package1',
|
||||||
|
'1.0.0',
|
||||||
|
:'repo1ID'
|
||||||
|
);
|
||||||
|
insert into snapshot (
|
||||||
|
package_id,
|
||||||
|
version,
|
||||||
|
content_url
|
||||||
|
) values (
|
||||||
|
:'package1ID',
|
||||||
|
'1.0.0',
|
||||||
|
'package1_1.0.0_url'
|
||||||
|
);
|
||||||
|
insert into package (
|
||||||
|
package_id,
|
||||||
|
name,
|
||||||
|
latest_version,
|
||||||
|
repository_id
|
||||||
|
) values (
|
||||||
|
:'package2ID',
|
||||||
|
'package2',
|
||||||
|
'1.0.0',
|
||||||
|
:'repo2ID'
|
||||||
|
);
|
||||||
|
insert into snapshot (
|
||||||
|
package_id,
|
||||||
|
version,
|
||||||
|
content_url
|
||||||
|
) values (
|
||||||
|
:'package2ID',
|
||||||
|
'1.0.0',
|
||||||
|
'package2_1.0.0_url'
|
||||||
|
);
|
||||||
|
insert into package (
|
||||||
|
package_id,
|
||||||
|
name,
|
||||||
|
latest_version,
|
||||||
|
repository_id
|
||||||
|
) values (
|
||||||
|
:'package3ID',
|
||||||
|
'package3',
|
||||||
|
'1.0.0',
|
||||||
|
:'repo2ID'
|
||||||
|
);
|
||||||
|
insert into snapshot (
|
||||||
|
package_id,
|
||||||
|
version
|
||||||
|
) values (
|
||||||
|
:'package3ID',
|
||||||
|
'1.0.0'
|
||||||
|
);
|
||||||
|
insert into package (
|
||||||
|
package_id,
|
||||||
|
name,
|
||||||
|
latest_version,
|
||||||
|
repository_id
|
||||||
|
) values (
|
||||||
|
:'package4ID',
|
||||||
|
'package4',
|
||||||
|
'1.0.0',
|
||||||
|
:'repo3ID'
|
||||||
|
);
|
||||||
|
insert into snapshot (
|
||||||
|
package_id,
|
||||||
|
version,
|
||||||
|
content_url
|
||||||
|
) values (
|
||||||
|
:'package4ID',
|
||||||
|
'1.0.0',
|
||||||
|
'package4_1.0.0_url'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Run some tests
|
||||||
|
select is(
|
||||||
|
get_harbor_replication_dump()::jsonb,
|
||||||
|
'[
|
||||||
|
{
|
||||||
|
"repository": "repo1",
|
||||||
|
"package": "package1",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"url": "package1_1.0.0_url"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"repository": "repo2",
|
||||||
|
"package": "package2",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"url": "package2_1.0.0_url"
|
||||||
|
}
|
||||||
|
]'::jsonb,
|
||||||
|
'Two packages expected in dump'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Finish tests and rollback transaction
|
||||||
|
select * from finish();
|
||||||
|
rollback;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
-- Start transaction and plan tests
|
-- Start transaction and plan tests
|
||||||
begin;
|
begin;
|
||||||
select plan(131);
|
select plan(132);
|
||||||
|
|
||||||
-- Check default_text_search_config is correct
|
-- Check default_text_search_config is correct
|
||||||
select results_eq(
|
select results_eq(
|
||||||
|
|
@ -372,6 +372,7 @@ select has_function('update_organization');
|
||||||
select has_function('user_belongs_to_organization');
|
select has_function('user_belongs_to_organization');
|
||||||
-- Packages
|
-- Packages
|
||||||
select has_function('generate_package_tsdoc');
|
select has_function('generate_package_tsdoc');
|
||||||
|
select has_function('get_harbor_replication_dump');
|
||||||
select has_function('get_package');
|
select has_function('get_package');
|
||||||
select has_function('get_package_changelog');
|
select has_function('get_package_changelog');
|
||||||
select has_function('get_package_summary');
|
select has_function('get_package_summary');
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ tags:
|
||||||
description: ""
|
description: ""
|
||||||
- name: Availability checks
|
- name: Availability checks
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: Integrations
|
||||||
|
description: ""
|
||||||
paths:
|
paths:
|
||||||
/users:
|
/users:
|
||||||
post:
|
post:
|
||||||
|
|
@ -1841,6 +1843,84 @@ paths:
|
||||||
$ref: "#/components/responses/TooManyRequests"
|
$ref: "#/components/responses/TooManyRequests"
|
||||||
"500":
|
"500":
|
||||||
$ref: "#/components/responses/InternalServerError"
|
$ref: "#/components/responses/InternalServerError"
|
||||||
|
/harborReplication:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Integrations
|
||||||
|
summary: Get Harbor replication dump
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- repository
|
||||||
|
- package
|
||||||
|
- version
|
||||||
|
- url
|
||||||
|
properties:
|
||||||
|
repository:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
package:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
nullable: false
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
nullable: false
|
||||||
|
example:
|
||||||
|
- repository: bitnami
|
||||||
|
package: nginx-ingress-controller
|
||||||
|
version: 5.6.10
|
||||||
|
url: https://charts.bitnami.com/bitnami/nginx-ingress-controller-5.6.10.tgz
|
||||||
|
- repository: bitnami
|
||||||
|
package: mediawiki
|
||||||
|
version: 6.3.6
|
||||||
|
url: https://charts.bitnami.com/bitnami/mediawiki-6.3.6.tgz
|
||||||
|
- repository: crossplane
|
||||||
|
package: crossplane
|
||||||
|
version: 0.0.0-793.c9c71f8
|
||||||
|
url: https://charts.crossplane.io/master/crossplane-0.0.0-793.c9c71f8.tgz
|
||||||
|
- repository: choerodon
|
||||||
|
package: devops-service
|
||||||
|
version: 0.22.0
|
||||||
|
url: https://openchart.choerodon.com.cn/choerodon/c7n/charts/devops-service-0.22.0.tgz
|
||||||
|
- repository: t3n
|
||||||
|
package: snipeit
|
||||||
|
version: 2.4.0
|
||||||
|
url: https://storage.googleapis.com/t3n-helm-charts/snipeit-2.4.0.tgz
|
||||||
|
- repository: bitnami
|
||||||
|
package: osclass
|
||||||
|
version: 7.0.10
|
||||||
|
url: https://charts.bitnami.com/bitnami/osclass-7.0.10.tgz
|
||||||
|
- repository: choerodon
|
||||||
|
package: asgard-service
|
||||||
|
version: 0.13.0
|
||||||
|
url: https://openchart.choerodon.com.cn/choerodon/c7n/charts/asgard-service-0.13.0.tgz
|
||||||
|
- repository: bitnami
|
||||||
|
package: osclass
|
||||||
|
version: 3.2.0
|
||||||
|
url: https://charts.bitnami.com/bitnami/osclass-3.2.0.tgz
|
||||||
|
- repository: cronce
|
||||||
|
package: torrentor
|
||||||
|
version: 0.3.0
|
||||||
|
url: https://charts.cronce.io/charts/torrentor-0.3.0.tgz
|
||||||
|
- repository: banzaicloud-stable
|
||||||
|
package: kafka-operator
|
||||||
|
version: 0.0.12
|
||||||
|
url: https://kubernetes-charts.banzaicloud.com/charts/kafka-operator-0.0.12.tgz
|
||||||
|
"429":
|
||||||
|
$ref: "#/components/responses/TooManyRequests"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/InternalServerError"
|
||||||
components:
|
components:
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
ApiKeyAuth:
|
ApiKeyAuth:
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ type Package struct {
|
||||||
type PackageManager interface {
|
type PackageManager interface {
|
||||||
Get(ctx context.Context, input *GetPackageInput) (*Package, error)
|
Get(ctx context.Context, input *GetPackageInput) (*Package, error)
|
||||||
GetChangeLogJSON(ctx context.Context, pkgID string) ([]byte, error)
|
GetChangeLogJSON(ctx context.Context, pkgID string) ([]byte, error)
|
||||||
|
GetHarborReplicationDumpJSON(ctx context.Context) ([]byte, error)
|
||||||
GetJSON(ctx context.Context, input *GetPackageInput) ([]byte, error)
|
GetJSON(ctx context.Context, input *GetPackageInput) ([]byte, error)
|
||||||
GetRandomJSON(ctx context.Context) ([]byte, error)
|
GetRandomJSON(ctx context.Context) ([]byte, error)
|
||||||
GetSnapshotSecurityReportJSON(ctx context.Context, pkgID, version string) ([]byte, error)
|
GetSnapshotSecurityReportJSON(ctx context.Context, pkgID, version string) ([]byte, error)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Database queries
|
// Database queries
|
||||||
|
getHarborReplicationDumpDBQ = `select get_harbor_replication_dump()`
|
||||||
getPkgDBQ = `select get_package($1::jsonb)`
|
getPkgDBQ = `select get_package($1::jsonb)`
|
||||||
getPkgChangeLogDBQ = `select get_package_changelog($1::uuid)`
|
getPkgChangeLogDBQ = `select get_package_changelog($1::uuid)`
|
||||||
getPkgStarsDBQ = `select get_package_stars($1::uuid, $2::uuid)`
|
getPkgStarsDBQ = `select get_package_stars($1::uuid, $2::uuid)`
|
||||||
|
|
@ -73,6 +74,12 @@ func (m *Manager) GetChangeLogJSON(ctx context.Context, pkgID string) ([]byte, e
|
||||||
return util.DBQueryJSON(ctx, m.db, getPkgChangeLogDBQ, pkgID)
|
return util.DBQueryJSON(ctx, m.db, getPkgChangeLogDBQ, pkgID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHarborReplicationDumpJSON returns a json list with all packages versions
|
||||||
|
// of kind Helm available so that they can be synchronized in Harbor.
|
||||||
|
func (m *Manager) GetHarborReplicationDumpJSON(ctx context.Context) ([]byte, error) {
|
||||||
|
return util.DBQueryJSON(ctx, m.db, getHarborReplicationDumpDBQ)
|
||||||
|
}
|
||||||
|
|
||||||
// GetJSON returns the package identified by the input provided as a json
|
// GetJSON returns the package identified by the input provided as a json
|
||||||
// object. The json object is built by the database.
|
// object. The json object is built by the database.
|
||||||
func (m *Manager) GetJSON(ctx context.Context, input *hub.GetPackageInput) ([]byte, error) {
|
func (m *Manager) GetJSON(ctx context.Context, input *hub.GetPackageInput) ([]byte, error) {
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,34 @@ func TestGetChangeLogJSON(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetHarborReplicationDumpJSON(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("database query succeeded", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
db := &tests.DBMock{}
|
||||||
|
db.On("QueryRow", ctx, getHarborReplicationDumpDBQ).Return([]byte("dataJSON"), nil)
|
||||||
|
m := NewManager(db)
|
||||||
|
|
||||||
|
dataJSON, err := m.GetHarborReplicationDumpJSON(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []byte("dataJSON"), dataJSON)
|
||||||
|
db.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("database error", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
db := &tests.DBMock{}
|
||||||
|
db.On("QueryRow", ctx, getHarborReplicationDumpDBQ).Return(nil, tests.ErrFakeDB)
|
||||||
|
m := NewManager(db)
|
||||||
|
|
||||||
|
dataJSON, err := m.GetHarborReplicationDumpJSON(ctx)
|
||||||
|
assert.Equal(t, tests.ErrFakeDB, err)
|
||||||
|
assert.Nil(t, dataJSON)
|
||||||
|
db.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetJSON(t *testing.T) {
|
func TestGetJSON(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ func (m *ManagerMock) GetChangeLogJSON(ctx context.Context, pkgID string) ([]byt
|
||||||
return data, args.Error(1)
|
return data, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHarborReplicationDumpJSON implements the PackageManager interface.
|
||||||
|
func (m *ManagerMock) GetHarborReplicationDumpJSON(ctx context.Context) ([]byte, error) {
|
||||||
|
args := m.Called(ctx)
|
||||||
|
data, _ := args.Get(0).([]byte)
|
||||||
|
return data, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
// GetJSON implements the PackageManager interface.
|
// GetJSON implements the PackageManager interface.
|
||||||
func (m *ManagerMock) GetJSON(ctx context.Context, input *hub.GetPackageInput) ([]byte, error) {
|
func (m *ManagerMock) GetJSON(ctx context.Context, input *hub.GetPackageInput) ([]byte, error) {
|
||||||
args := m.Called(ctx, input)
|
args := m.Called(ctx, input)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue