Add async third-party vetting
Queue vetting on third_parties with PENDING, PROCESSING, COMPLETED, and FAILED states. Expose enqueue and status through GraphQL, MCP, CLI, and n8n, validate vet requests, tune the worker via config, and poll the detail page while vetting runs. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -987,18 +987,17 @@ func TestThirdParty_OmittableWebsiteUrl(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestThirdParty_Assess exercises the assessThirdParty mutation through authorization
|
||||
// and tenant-isolation paths without running the real LLM/browser pipeline.
|
||||
// The e2e config deliberately omits `llm.third-party-assessor.provider`, so an
|
||||
// authorized call reaches DisabledThirdPartyAssessor and surfaces a stable
|
||||
// UNAVAILABLE error. Happy-path payload shape is covered by unit tests in
|
||||
// pkg/probo.
|
||||
func TestThirdParty_Assess(t *testing.T) {
|
||||
// TestThirdParty_Vet exercises the vetThirdParty mutation through authorization
|
||||
// and tenant-isolation paths without running the real LLM/browser pipeline to
|
||||
// completion. The e2e config sets OPENAI_API_KEY and inherits the default
|
||||
// agent provider, so authorized calls enqueue vetting and return the third
|
||||
// party. Request validation is covered by unit tests in pkg/thirdparty.
|
||||
func TestThirdParty_Vet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const query = `
|
||||
mutation AssessThirdParty($input: AssessThirdPartyInput!) {
|
||||
assessThirdParty(input: $input) {
|
||||
mutation VetThirdParty($input: VetThirdPartyInput!) {
|
||||
vetThirdParty(input: $input) {
|
||||
thirdParty {
|
||||
id
|
||||
}
|
||||
@@ -1007,18 +1006,18 @@ func TestThirdParty_Assess(t *testing.T) {
|
||||
`
|
||||
|
||||
type resultShape struct {
|
||||
AssessThirdParty struct {
|
||||
VetThirdParty struct {
|
||||
ThirdParty struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"thirdParty"`
|
||||
} `json:"assessThirdParty"`
|
||||
} `json:"vetThirdParty"`
|
||||
}
|
||||
|
||||
t.Run("owner call surfaces the disabled error", func(t *testing.T) {
|
||||
t.Run("owner call enqueues vetting", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
thirdPartyID := factory.NewThirdParty(owner).WithName("Unconfigured assess").Create()
|
||||
thirdPartyID := factory.NewThirdParty(owner).WithName("Unconfigured vet").Create()
|
||||
|
||||
var result resultShape
|
||||
|
||||
@@ -1028,15 +1027,16 @@ func TestThirdParty_Assess(t *testing.T) {
|
||||
"websiteUrl": "https://thirdParty.example.com",
|
||||
},
|
||||
}, &result)
|
||||
testutil.RequireErrorCode(t, err, "UNAVAILABLE")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, thirdPartyID, result.VetThirdParty.ThirdParty.ID)
|
||||
})
|
||||
|
||||
t.Run("admin call surfaces the disabled error", func(t *testing.T) {
|
||||
t.Run("admin call enqueues vetting", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
admin := testutil.NewClientInOrg(t, testutil.RoleAdmin, owner)
|
||||
thirdPartyID := factory.NewThirdParty(owner).WithName("Admin-assessed thirdParty").Create()
|
||||
thirdPartyID := factory.NewThirdParty(owner).WithName("Admin-vetted thirdParty").Create()
|
||||
|
||||
var result resultShape
|
||||
|
||||
@@ -1046,10 +1046,11 @@ func TestThirdParty_Assess(t *testing.T) {
|
||||
"websiteUrl": "https://admin.example.com",
|
||||
},
|
||||
}, &result)
|
||||
testutil.RequireErrorCode(t, err, "UNAVAILABLE")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, thirdPartyID, result.VetThirdParty.ThirdParty.ID)
|
||||
})
|
||||
|
||||
t.Run("viewer cannot assess a thirdParty", func(t *testing.T) {
|
||||
t.Run("viewer cannot vet a thirdParty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
@@ -1067,7 +1068,7 @@ func TestThirdParty_Assess(t *testing.T) {
|
||||
testutil.RequireForbiddenError(t, err)
|
||||
})
|
||||
|
||||
t.Run("cannot assess thirdParty from another organization", func(t *testing.T) {
|
||||
t.Run("cannot vet thirdParty from another organization", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
org1Owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
@@ -1082,7 +1083,7 @@ func TestThirdParty_Assess(t *testing.T) {
|
||||
"websiteUrl": "https://cross-tenant.example.com",
|
||||
},
|
||||
}, &result)
|
||||
require.Error(t, err, "thirdParty assess must not cross tenant boundaries")
|
||||
require.Error(t, err, "thirdParty vet must not cross tenant boundaries")
|
||||
})
|
||||
|
||||
t.Run("procedure is accepted on the input", func(t *testing.T) {
|
||||
@@ -1100,7 +1101,8 @@ func TestThirdParty_Assess(t *testing.T) {
|
||||
"procedure": "Focus on SOC 2 controls and data residency",
|
||||
},
|
||||
}, &result)
|
||||
testutil.RequireErrorCode(t, err, "UNAVAILABLE")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, thirdPartyID, result.VetThirdParty.ThirdParty.ID)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user