Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
880cb899b5 | ||
|
|
779bb70301 | ||
|
|
2487072d95 | ||
|
|
ad927afbc1 | ||
|
|
b086f8c3ff | ||
|
|
d9bf48be7e | ||
|
|
fe261b22c5 | ||
|
|
18e3661289 | ||
|
|
d87e24d839 | ||
|
|
b3775e2feb | ||
|
|
4fc5b5b146 | ||
|
|
0fe2588e78 | ||
|
|
d9d993d267 | ||
|
|
1b842a1c60 | ||
|
|
64de1d9e17 | ||
|
|
48c0ab5175 | ||
|
|
eb3ab3aebc | ||
|
|
df423d5e18 | ||
|
|
23de1d53dd | ||
|
|
963dd4a11d | ||
|
|
e96f74e634 | ||
|
|
b180dee597 | ||
|
|
44d4821a8d | ||
|
|
6bbb46b29a | ||
|
|
f197d7cf7b | ||
|
|
6c9de9bef3 | ||
|
|
686867bd96 | ||
|
|
cd41d2fbce | ||
|
|
2b79c1b797 | ||
|
|
2ab6d34538 | ||
|
|
e0884c734c | ||
|
|
f3fdb4a885 | ||
|
|
f4dbaf60b0 | ||
|
|
b9b5650506 | ||
|
|
f28e79c098 | ||
|
|
42af271526 | ||
|
|
d87435fdc2 | ||
|
|
8bdf86bbad | ||
|
|
bf52fdbe6a | ||
|
|
90d6db486b | ||
|
|
f10730cf98 | ||
|
|
bb2bd6df3f | ||
|
|
5e8407a970 | ||
|
|
4b56754546 | ||
|
|
28aba60cc5 | ||
|
|
03048a6ca8 | ||
|
|
97535e2afc | ||
|
|
cd7450395e | ||
|
|
411ef24024 | ||
|
|
b3b52c0dfc | ||
|
|
8502e1fee6 | ||
|
|
f0b8041c3f | ||
|
|
f92f811559 | ||
|
|
71a9087e4d | ||
|
|
45886e2ad9 | ||
|
|
7262d3b284 | ||
|
|
2ab3c890b4 | ||
|
|
f00dd0f88a | ||
|
|
a2d40b4d38 | ||
|
|
c1ec1585a2 | ||
|
|
a680f13f84 | ||
|
|
9c8ac96aed | ||
|
|
e6dd387811 |
@ -1,4 +1,5 @@
|
|||||||
name: Backup Restore Test
|
name: Backup Restore Test
|
||||||
|
description: Run various backup/restore/export tests for a service.
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
service:
|
service:
|
||||||
|
|||||||
1
.github/actions/go-setup-cache/action.yml
vendored
1
.github/actions/go-setup-cache/action.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
name: Setup and Cache Golang
|
name: Setup and Cache Golang
|
||||||
|
description: Build golang binaries for later use in CI.
|
||||||
|
|
||||||
# clone of: https://github.com/magnetikonline/action-golang-cache/blob/main/action.yaml
|
# clone of: https://github.com/magnetikonline/action-golang-cache/blob/main/action.yaml
|
||||||
#
|
#
|
||||||
|
|||||||
1
.github/actions/publish-binary/action.yml
vendored
1
.github/actions/publish-binary/action.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
name: Publish Binary
|
name: Publish Binary
|
||||||
|
description: Publish binary artifacts.
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
|
|||||||
1
.github/actions/publish-website/action.yml
vendored
1
.github/actions/publish-website/action.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
name: Publish Website
|
name: Publish Website
|
||||||
|
description: Publish website artifacts.
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
aws-iam-role:
|
aws-iam-role:
|
||||||
|
|||||||
44
.github/actions/purge-m365-data/action.yml
vendored
44
.github/actions/purge-m365-data/action.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
name: Purge M365 User Data
|
name: Purge M365 User Data
|
||||||
|
description: Deletes M365 data generated during CI tests.
|
||||||
|
|
||||||
# Hard deletion of an m365 user's data. Our CI processes create a lot
|
# Hard deletion of an m365 user's data. Our CI processes create a lot
|
||||||
# of data churn (creation and immediate deletion) of files, the likes
|
# of data churn (creation and immediate deletion) of files, the likes
|
||||||
@ -30,12 +31,19 @@ inputs:
|
|||||||
description: Secret value of for AZURE_CLIENT_ID
|
description: Secret value of for AZURE_CLIENT_ID
|
||||||
azure-client-secret:
|
azure-client-secret:
|
||||||
description: Secret value of for AZURE_CLIENT_SECRET
|
description: Secret value of for AZURE_CLIENT_SECRET
|
||||||
|
azure-pnp-client-id:
|
||||||
|
description: Secret value of AZURE_PNP_CLIENT_ID
|
||||||
|
azure-pnp-client-cert:
|
||||||
|
description: Base64 encoded private certificate for the azure-pnp-client-id (Secret value of AZURE_PNP_CLIENT_CERT)
|
||||||
azure-tenant-id:
|
azure-tenant-id:
|
||||||
description: Secret value of for AZURE_TENANT_ID
|
description: Secret value of AZURE_TENANT_ID
|
||||||
m365-admin-user:
|
m365-admin-user:
|
||||||
description: Secret value of for M365_TENANT_ADMIN_USER
|
description: Secret value of for M365_TENANT_ADMIN_USER
|
||||||
m365-admin-password:
|
m365-admin-password:
|
||||||
description: Secret value of for M365_TENANT_ADMIN_PASSWORD
|
description: Secret value of for M365_TENANT_ADMIN_PASSWORD
|
||||||
|
tenant-domain:
|
||||||
|
description: The domain of the tenant (ex. 10rqc2.onmicrosft.com)
|
||||||
|
required: true
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
@ -53,7 +61,13 @@ runs:
|
|||||||
AZURE_CLIENT_ID: ${{ inputs.azure-client-id }}
|
AZURE_CLIENT_ID: ${{ inputs.azure-client-id }}
|
||||||
AZURE_CLIENT_SECRET: ${{ inputs.azure-client-secret }}
|
AZURE_CLIENT_SECRET: ${{ inputs.azure-client-secret }}
|
||||||
AZURE_TENANT_ID: ${{ inputs.azure-tenant-id }}
|
AZURE_TENANT_ID: ${{ inputs.azure-tenant-id }}
|
||||||
run: ./exchangePurge.ps1 -User ${{ inputs.user }} -FolderNamePurgeList PersonMetadata -FolderPrefixPurgeList "${{ inputs.folder-prefix }}".Split(",") -PurgeBeforeTimestamp ${{ inputs.older-than }}
|
run: |
|
||||||
|
for ($ATTEMPT_NUM = 1; $ATTEMPT_NUM -le 3; $ATTEMPT_NUM++)
|
||||||
|
{
|
||||||
|
if (./exchangePurge.ps1 -User ${{ inputs.user }} -FolderNamePurgeList PersonMetadata -FolderPrefixPurgeList "${{ inputs.folder-prefix }}".Split(",") -PurgeBeforeTimestamp ${{ inputs.older-than }}) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# TODO(ashmrtn): Re-enable when we figure out errors we're seeing with Get-Mailbox call.
|
# TODO(ashmrtn): Re-enable when we figure out errors we're seeing with Get-Mailbox call.
|
||||||
#- name: Reset retention for all mailboxes to 0
|
#- name: Reset retention for all mailboxes to 0
|
||||||
@ -74,10 +88,16 @@ runs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
working-directory: ./src/cmd/purge/scripts
|
working-directory: ./src/cmd/purge/scripts
|
||||||
env:
|
env:
|
||||||
M365_TENANT_ADMIN_USER: ${{ inputs.m365-admin-user }}
|
AZURE_CLIENT_ID: ${{ inputs.azure-pnp-client-id }}
|
||||||
M365_TENANT_ADMIN_PASSWORD: ${{ inputs.m365-admin-password }}
|
AZURE_APP_CERT: ${{ inputs.azure-pnp-client-cert }}
|
||||||
|
TENANT_DOMAIN: ${{ inputs.tenant-domain }}
|
||||||
run: |
|
run: |
|
||||||
./onedrivePurge.ps1 -User ${{ inputs.user }} -FolderPrefixPurgeList "${{ inputs.folder-prefix }}".Split(",") -PurgeBeforeTimestamp ${{ inputs.older-than }}
|
for ($ATTEMPT_NUM = 1; $ATTEMPT_NUM -le 3; $ATTEMPT_NUM++)
|
||||||
|
{
|
||||||
|
if (./onedrivePurge.ps1 -User ${{ inputs.user }} -FolderPrefixPurgeList "${{ inputs.folder-prefix }}".Split(",") -PurgeBeforeTimestamp ${{ inputs.older-than }}) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
################################################################################################################
|
################################################################################################################
|
||||||
# Sharepoint
|
# Sharepoint
|
||||||
@ -88,6 +108,14 @@ runs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
working-directory: ./src/cmd/purge/scripts
|
working-directory: ./src/cmd/purge/scripts
|
||||||
env:
|
env:
|
||||||
M365_TENANT_ADMIN_USER: ${{ inputs.m365-admin-user }}
|
AZURE_CLIENT_ID: ${{ inputs.azure-pnp-client-id }}
|
||||||
M365_TENANT_ADMIN_PASSWORD: ${{ inputs.m365-admin-password }}
|
AZURE_APP_CERT: ${{ inputs.azure-pnp-client-cert }}
|
||||||
run: ./onedrivePurge.ps1 -Site ${{ inputs.site }} -LibraryNameList "${{ inputs.libraries }}".split(",") -FolderPrefixPurgeList ${{ inputs.folder-prefix }} -LibraryPrefixDeleteList ${{ inputs.library-prefix && inputs.library-prefix || '[]' }} -PurgeBeforeTimestamp ${{ inputs.older-than }}
|
TENANT_DOMAIN: ${{ inputs.tenant-domain }}
|
||||||
|
run: |
|
||||||
|
for ($ATTEMPT_NUM = 1; $ATTEMPT_NUM -le 3; $ATTEMPT_NUM++)
|
||||||
|
{
|
||||||
|
if (./onedrivePurge.ps1 -Site ${{ inputs.site }} -LibraryNameList "${{ inputs.libraries }}".split(",") -FolderPrefixPurgeList ${{ inputs.folder-prefix }} -LibraryPrefixDeleteList ${{ inputs.library-prefix && inputs.library-prefix || '[]' }} -PurgeBeforeTimestamp ${{ inputs.older-than }}) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
1
.github/actions/teams-message/action.yml
vendored
1
.github/actions/teams-message/action.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
name: Send a message to Teams
|
name: Send a message to Teams
|
||||||
|
description: Send messages to communication apps.
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
msg:
|
msg:
|
||||||
|
|||||||
1
.github/actions/website-linting/action.yml
vendored
1
.github/actions/website-linting/action.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
name: Lint Website
|
name: Lint Website
|
||||||
|
description: Lint website content.
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
|
|||||||
2
.github/workflows/binary-publish.yml
vendored
2
.github/workflows/binary-publish.yml
vendored
@ -40,5 +40,5 @@ jobs:
|
|||||||
if: failure()
|
if: failure()
|
||||||
uses: ./.github/actions/teams-message
|
uses: ./.github/actions/teams-message
|
||||||
with:
|
with:
|
||||||
msg: "[FAILED] Publishing Binary"
|
msg: "[CORSO FAILED] Publishing Binary"
|
||||||
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
||||||
|
|||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -463,7 +463,7 @@ jobs:
|
|||||||
go-version-file: src/go.mod
|
go-version-file: src/go.mod
|
||||||
|
|
||||||
- name: Go Lint
|
- name: Go Lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v4
|
||||||
with:
|
with:
|
||||||
# Keep pinned to a verson as sometimes updates will add new lint
|
# Keep pinned to a verson as sometimes updates will add new lint
|
||||||
# failures in unchanged code.
|
# failures in unchanged code.
|
||||||
|
|||||||
12
.github/workflows/ci_test_cleanup.yml
vendored
12
.github/workflows/ci_test_cleanup.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
user: [ CORSO_M365_TEST_USER_ID, CORSO_SECONDARY_M365_TEST_USER_ID, '' ]
|
user: [CORSO_M365_TEST_USER_ID, CORSO_SECONDARY_M365_TEST_USER_ID, ""]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@ -33,12 +33,15 @@ jobs:
|
|||||||
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
||||||
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
||||||
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
||||||
|
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
|
||||||
|
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
|
||||||
|
tenant-domain: ${{ vars.TENANT_DOMAIN }}
|
||||||
|
|
||||||
- name: Notify failure in teams
|
- name: Notify failure in teams
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: ./.github/actions/teams-message
|
uses: ./.github/actions/teams-message
|
||||||
with:
|
with:
|
||||||
msg: "[FAILED] ${{ vars[matrix.user] }} CI Cleanup"
|
msg: "[CORSO FAILED] ${{ vars[matrix.user] }} CI Cleanup"
|
||||||
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
||||||
|
|
||||||
Test-Site-Data-Cleanup:
|
Test-Site-Data-Cleanup:
|
||||||
@ -70,10 +73,13 @@ jobs:
|
|||||||
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
||||||
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
||||||
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
||||||
|
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
|
||||||
|
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
|
||||||
|
tenant-domain: ${{ vars.TENANT_DOMAIN }}
|
||||||
|
|
||||||
- name: Notify failure in teams
|
- name: Notify failure in teams
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: ./.github/actions/teams-message
|
uses: ./.github/actions/teams-message
|
||||||
with:
|
with:
|
||||||
msg: "[FAILED] ${{ vars[matrix.site] }} CI Cleanup"
|
msg: "[CORSO FAILED] ${{ vars[matrix.site] }} CI Cleanup"
|
||||||
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
||||||
|
|||||||
3
.github/workflows/load_test.yml
vendored
3
.github/workflows/load_test.yml
vendored
@ -155,3 +155,6 @@ jobs:
|
|||||||
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
||||||
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
||||||
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
||||||
|
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
|
||||||
|
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
|
||||||
|
tenant-domain: ${{ vars.TENANT_DOMAIN }}
|
||||||
|
|||||||
7
.github/workflows/longevity_test.yml
vendored
7
.github/workflows/longevity_test.yml
vendored
@ -6,7 +6,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
user:
|
user:
|
||||||
description: 'User to run longevity test on'
|
description: "User to run longevity test on"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
# required to retrieve AWS credentials
|
# required to retrieve AWS credentials
|
||||||
@ -37,7 +37,7 @@ jobs:
|
|||||||
CORSO_LOG_FILE: ${{ github.workspace }}/src/testlog/run-longevity.log
|
CORSO_LOG_FILE: ${{ github.workspace }}/src/testlog/run-longevity.log
|
||||||
RESTORE_DEST_PFX: Corso_Test_Longevity_
|
RESTORE_DEST_PFX: Corso_Test_Longevity_
|
||||||
TEST_USER: ${{ github.event.inputs.user != '' && github.event.inputs.user || vars.CORSO_M365_TEST_USER_ID }}
|
TEST_USER: ${{ github.event.inputs.user != '' && github.event.inputs.user || vars.CORSO_M365_TEST_USER_ID }}
|
||||||
PREFIX: 'longevity'
|
PREFIX: "longevity"
|
||||||
|
|
||||||
# Options for retention.
|
# Options for retention.
|
||||||
RETENTION_MODE: GOVERNANCE
|
RETENTION_MODE: GOVERNANCE
|
||||||
@ -113,7 +113,6 @@ jobs:
|
|||||||
--extend-retention \
|
--extend-retention \
|
||||||
--prefix ${{ env.PREFIX }} \
|
--prefix ${{ env.PREFIX }} \
|
||||||
--bucket ${{ secrets.CI_RETENTION_TESTS_S3_BUCKET }} \
|
--bucket ${{ secrets.CI_RETENTION_TESTS_S3_BUCKET }} \
|
||||||
--succeed-if-exists \
|
|
||||||
2>&1 | tee ${{ env.CORSO_LOG_DIR }}/gotest-repo-init.log
|
2>&1 | tee ${{ env.CORSO_LOG_DIR }}/gotest-repo-init.log
|
||||||
|
|
||||||
if grep -q 'Failed to' ${{ env.CORSO_LOG_DIR }}/gotest-repo-init.log
|
if grep -q 'Failed to' ${{ env.CORSO_LOG_DIR }}/gotest-repo-init.log
|
||||||
@ -393,5 +392,5 @@ jobs:
|
|||||||
if: failure()
|
if: failure()
|
||||||
uses: ./.github/actions/teams-message
|
uses: ./.github/actions/teams-message
|
||||||
with:
|
with:
|
||||||
msg: "[FAILED] Longevity Test"
|
msg: "[CORSO FAILED] Longevity Test"
|
||||||
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
||||||
|
|||||||
2
.github/workflows/nightly_test.yml
vendored
2
.github/workflows/nightly_test.yml
vendored
@ -118,5 +118,5 @@ jobs:
|
|||||||
if: failure()
|
if: failure()
|
||||||
uses: ./.github/actions/teams-message
|
uses: ./.github/actions/teams-message
|
||||||
with:
|
with:
|
||||||
msg: "[FAILED] Nightly Checks"
|
msg: "[COROS FAILED] Nightly Checks"
|
||||||
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
||||||
|
|||||||
52
.github/workflows/sanity-test.yaml
vendored
52
.github/workflows/sanity-test.yaml
vendored
@ -6,7 +6,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
user:
|
user:
|
||||||
description: 'User to run sanity test on'
|
description: "User to run sanity test on"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
# required to retrieve AWS credentials
|
# required to retrieve AWS credentials
|
||||||
@ -48,7 +48,6 @@ jobs:
|
|||||||
|
|
||||||
# setup
|
# setup
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Golang with cache
|
- name: Setup Golang with cache
|
||||||
@ -91,6 +90,9 @@ jobs:
|
|||||||
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
||||||
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
||||||
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
||||||
|
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
|
||||||
|
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
|
||||||
|
tenant-domain: ${{ vars.TENANT_DOMAIN }}
|
||||||
|
|
||||||
- name: Purge CI-Produced Folders for Sites
|
- name: Purge CI-Produced Folders for Sites
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
@ -106,6 +108,9 @@ jobs:
|
|||||||
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
azure-tenant-id: ${{ secrets.TENANT_ID }}
|
||||||
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
m365-admin-user: ${{ secrets.M365_TENANT_ADMIN_USER }}
|
||||||
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
m365-admin-password: ${{ secrets.M365_TENANT_ADMIN_PASSWORD }}
|
||||||
|
azure-pnp-client-id: ${{ secrets.AZURE_PNP_CLIENT_ID }}
|
||||||
|
azure-pnp-client-cert: ${{ secrets.AZURE_PNP_CLIENT_CERT }}
|
||||||
|
tenant-domain: ${{ vars.TENANT_DOMAIN }}
|
||||||
|
|
||||||
##########################################################################################################################################
|
##########################################################################################################################################
|
||||||
|
|
||||||
@ -193,8 +198,8 @@ jobs:
|
|||||||
service: exchange
|
service: exchange
|
||||||
kind: first-backup
|
kind: first-backup
|
||||||
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
|
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
|
||||||
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
|
restore-args: "--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
|
|
||||||
@ -206,8 +211,8 @@ jobs:
|
|||||||
service: exchange
|
service: exchange
|
||||||
kind: incremental
|
kind: incremental
|
||||||
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
|
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
|
||||||
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
|
restore-args: "--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
|
||||||
backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
|
backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
@ -220,8 +225,8 @@ jobs:
|
|||||||
service: exchange
|
service: exchange
|
||||||
kind: non-delta
|
kind: non-delta
|
||||||
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email" --disable-delta'
|
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email" --disable-delta'
|
||||||
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
|
restore-args: "--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
|
||||||
backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
|
backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
@ -234,13 +239,12 @@ jobs:
|
|||||||
service: exchange
|
service: exchange
|
||||||
kind: non-delta-incremental
|
kind: non-delta-incremental
|
||||||
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
|
backup-args: '--mailbox "${{ env.TEST_USER }}" --data "email"'
|
||||||
restore-args: '--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
|
restore-args: "--email-folder ${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.repo-init.outputs.result }}"
|
||||||
backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
|
backup-id: ${{ steps.exchange-backup.outputs.backup-id }}
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
|
|
||||||
|
|
||||||
##########################################################################################################################################
|
##########################################################################################################################################
|
||||||
|
|
||||||
# Onedrive
|
# Onedrive
|
||||||
@ -270,8 +274,8 @@ jobs:
|
|||||||
service: onedrive
|
service: onedrive
|
||||||
kind: first-backup
|
kind: first-backup
|
||||||
backup-args: '--user "${{ env.TEST_USER }}"'
|
backup-args: '--user "${{ env.TEST_USER }}"'
|
||||||
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}'
|
restore-args: "--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}"
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}"
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
|
|
||||||
@ -295,8 +299,8 @@ jobs:
|
|||||||
service: onedrive
|
service: onedrive
|
||||||
kind: incremental
|
kind: incremental
|
||||||
backup-args: '--user "${{ env.TEST_USER }}"'
|
backup-args: '--user "${{ env.TEST_USER }}"'
|
||||||
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}'
|
restore-args: "--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}"
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-onedrive.outputs.result }}"
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
|
|
||||||
@ -330,8 +334,8 @@ jobs:
|
|||||||
service: sharepoint
|
service: sharepoint
|
||||||
kind: first-backup
|
kind: first-backup
|
||||||
backup-args: '--site "${{ vars.CORSO_M365_TEST_SITE_URL }}" --data libraries'
|
backup-args: '--site "${{ vars.CORSO_M365_TEST_SITE_URL }}" --data libraries'
|
||||||
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}'
|
restore-args: "--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}"
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}"
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
category: libraries
|
category: libraries
|
||||||
@ -357,8 +361,8 @@ jobs:
|
|||||||
service: sharepoint
|
service: sharepoint
|
||||||
kind: incremental
|
kind: incremental
|
||||||
backup-args: '--site "${{ vars.CORSO_M365_TEST_SITE_URL }}" --data libraries'
|
backup-args: '--site "${{ vars.CORSO_M365_TEST_SITE_URL }}" --data libraries'
|
||||||
restore-args: '--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}'
|
restore-args: "--folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}"
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-sharepoint.outputs.result }}"
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
category: libraries
|
category: libraries
|
||||||
@ -483,8 +487,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
service: groups
|
service: groups
|
||||||
kind: first-backup
|
kind: first-backup
|
||||||
backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}"'
|
backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}" --data messages,libraries'
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}"
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
|
|
||||||
@ -508,9 +512,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
service: groups
|
service: groups
|
||||||
kind: incremental
|
kind: incremental
|
||||||
backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}"'
|
backup-args: '--group "${{ vars.CORSO_M365_TEST_TEAM_ID }}" --data messages,libraries'
|
||||||
restore-args: '--site "${{ vars.CORSO_M365_TEST_GROUPS_SITE_URL }}" --folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}'
|
restore-args: '--site "${{ vars.CORSO_M365_TEST_GROUPS_SITE_URL }}" --folder ${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}'
|
||||||
restore-container: '${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}'
|
restore-container: "${{ env.RESTORE_DEST_PFX }}${{ steps.new-data-creation-groups.outputs.result }}"
|
||||||
log-dir: ${{ env.CORSO_LOG_DIR }}
|
log-dir: ${{ env.CORSO_LOG_DIR }}
|
||||||
with-export: true
|
with-export: true
|
||||||
|
|
||||||
@ -532,5 +536,5 @@ jobs:
|
|||||||
if: failure()
|
if: failure()
|
||||||
uses: ./.github/actions/teams-message
|
uses: ./.github/actions/teams-message
|
||||||
with:
|
with:
|
||||||
msg: "[FAILED] Sanity Tests"
|
msg: "[CORSO FAILED] Sanity Tests"
|
||||||
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
teams_url: ${{ secrets.TEAMS_CORSO_CI_WEBHOOK_URL }}
|
||||||
|
|||||||
11
CHANGELOG.md
11
CHANGELOG.md
@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased] (beta)
|
## [Unreleased] (beta)
|
||||||
|
### Fixed
|
||||||
|
- Handle the case where an email or event cannot be retrieved from Exchange due to an `ErrorCorruptData` error. Corso will skip over the item but report it in the backup summary.
|
||||||
|
- Emails attached within other emails are now correctly exported
|
||||||
|
- Gracefully handle email and post attachments without name when exporting to eml
|
||||||
|
- Use correct timezone for event start and end times in Exchange exports (helps fix issues in relative recurrence patterns)
|
||||||
|
- Fixed an issue causing exports dealing with calendar data to have high memory usage
|
||||||
|
|
||||||
|
## [v0.19.0] (beta) - 2024-02-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Events can now be exported from Exchange backups as .ics files.
|
- Events can now be exported from Exchange backups as .ics files.
|
||||||
@ -494,7 +502,8 @@ this case, Corso will skip over the item but report this in the backup summary.
|
|||||||
- Miscellaneous
|
- Miscellaneous
|
||||||
- Optional usage statistics reporting ([RM-35](https://github.com/alcionai/corso-roadmap/issues/35))
|
- Optional usage statistics reporting ([RM-35](https://github.com/alcionai/corso-roadmap/issues/35))
|
||||||
|
|
||||||
[Unreleased]: https://github.com/alcionai/corso/compare/v0.18.0...HEAD
|
[Unreleased]: https://github.com/alcionai/corso/compare/v0.19.0...HEAD
|
||||||
|
[v0.19.0]: https://github.com/alcionai/corso/compare/v0.18.0...v0.19.0
|
||||||
[v0.18.0]: https://github.com/alcionai/corso/compare/v0.17.0...v0.18.0
|
[v0.18.0]: https://github.com/alcionai/corso/compare/v0.17.0...v0.18.0
|
||||||
[v0.17.0]: https://github.com/alcionai/corso/compare/v0.16.0...v0.17.0
|
[v0.17.0]: https://github.com/alcionai/corso/compare/v0.16.0...v0.17.0
|
||||||
[v0.16.0]: https://github.com/alcionai/corso/compare/v0.15.0...v0.16.0
|
[v0.16.0]: https://github.com/alcionai/corso/compare/v0.15.0...v0.16.0
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
> [!NOTE]
|
||||||
|
> **The Corso project is no longer actively maintained and has been archived**.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://github.com/alcionai/corso/blob/main/website/static/img/corso_logo.svg?raw=true" alt="Corso Logo" width="100" />
|
<img src="https://github.com/alcionai/corso/blob/main/website/static/img/corso_logo.svg?raw=true" alt="Corso Logo" width="100" />
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/internal/operations"
|
"github.com/alcionai/corso/src/internal/operations"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/config"
|
"github.com/alcionai/corso/src/pkg/config"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -39,7 +40,7 @@ var (
|
|||||||
type NoBackupExchangeE2ESuite struct {
|
type NoBackupExchangeE2ESuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoBackupExchangeE2ESuite(t *testing.T) {
|
func TestNoBackupExchangeE2ESuite(t *testing.T) {
|
||||||
@ -54,7 +55,7 @@ func (suite *NoBackupExchangeE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.ExchangeService)
|
suite.dpnd = prepM365Test(t, ctx, path.ExchangeService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ func (suite *NoBackupExchangeE2ESuite) TestExchangeBackupListCmd_noBackups() {
|
|||||||
type BackupExchangeE2ESuite struct {
|
type BackupExchangeE2ESuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupExchangeE2ESuite(t *testing.T) {
|
func TestBackupExchangeE2ESuite(t *testing.T) {
|
||||||
@ -108,7 +109,7 @@ func (suite *BackupExchangeE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.ExchangeService)
|
suite.dpnd = prepM365Test(t, ctx, path.ExchangeService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +139,7 @@ func runExchangeBackupCategoryTest(suite *BackupExchangeE2ESuite, category path.
|
|||||||
cmd, ctx := buildExchangeBackupCmd(
|
cmd, ctx := buildExchangeBackupCmd(
|
||||||
ctx,
|
ctx,
|
||||||
suite.dpnd.configFilePath,
|
suite.dpnd.configFilePath,
|
||||||
suite.its.user.ID,
|
suite.m365.User.ID,
|
||||||
category.String(),
|
category.String(),
|
||||||
&recorder)
|
&recorder)
|
||||||
|
|
||||||
@ -149,8 +150,11 @@ func runExchangeBackupCategoryTest(suite *BackupExchangeE2ESuite, category path.
|
|||||||
result := recorder.String()
|
result := recorder.String()
|
||||||
t.Log("backup results", result)
|
t.Log("backup results", result)
|
||||||
|
|
||||||
// as an offhand check: the result should contain the m365 user id
|
// As an offhand check: the result should contain the m365 user's email.
|
||||||
assert.Contains(t, result, suite.its.user.ID)
|
assert.Contains(
|
||||||
|
t,
|
||||||
|
strings.ToLower(result),
|
||||||
|
strings.ToLower(suite.m365.User.Provider.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_ServiceNotEnabled_email() {
|
func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_ServiceNotEnabled_email() {
|
||||||
@ -173,7 +177,7 @@ func runExchangeBackupServiceNotEnabledTest(suite *BackupExchangeE2ESuite, categ
|
|||||||
cmd, ctx := buildExchangeBackupCmd(
|
cmd, ctx := buildExchangeBackupCmd(
|
||||||
ctx,
|
ctx,
|
||||||
suite.dpnd.configFilePath,
|
suite.dpnd.configFilePath,
|
||||||
fmt.Sprintf("%s,%s", tconfig.UnlicensedM365UserID(suite.T()), suite.its.user.ID),
|
fmt.Sprintf("%s,%s", tconfig.UnlicensedM365UserID(suite.T()), suite.m365.User.ID),
|
||||||
category.String(),
|
category.String(),
|
||||||
&recorder)
|
&recorder)
|
||||||
err := cmd.ExecuteContext(ctx)
|
err := cmd.ExecuteContext(ctx)
|
||||||
@ -182,8 +186,11 @@ func runExchangeBackupServiceNotEnabledTest(suite *BackupExchangeE2ESuite, categ
|
|||||||
result := recorder.String()
|
result := recorder.String()
|
||||||
t.Log("backup results", result)
|
t.Log("backup results", result)
|
||||||
|
|
||||||
// as an offhand check: the result should contain the m365 user id
|
// As an offhand check: the result should contain the m365 user's email.
|
||||||
assert.Contains(t, result, suite.its.user.ID)
|
assert.Contains(
|
||||||
|
t,
|
||||||
|
strings.ToLower(result),
|
||||||
|
strings.ToLower(suite.m365.User.Provider.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_userNotFound_email() {
|
func (suite *BackupExchangeE2ESuite) TestExchangeBackupCmd_userNotFound_email() {
|
||||||
@ -242,7 +249,7 @@ func (suite *BackupExchangeE2ESuite) TestBackupCreateExchange_badAzureClientIDFl
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "exchange",
|
"backup", "create", "exchange",
|
||||||
"--user", suite.its.user.ID,
|
"--user", suite.m365.User.ID,
|
||||||
"--azure-client-id", "invalid-value")
|
"--azure-client-id", "invalid-value")
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
@ -266,7 +273,7 @@ func (suite *BackupExchangeE2ESuite) TestBackupCreateExchange_fromConfigFile() {
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "exchange",
|
"backup", "create", "exchange",
|
||||||
"--user", suite.its.user.ID,
|
"--user", suite.m365.User.ID,
|
||||||
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
@ -281,8 +288,11 @@ func (suite *BackupExchangeE2ESuite) TestBackupCreateExchange_fromConfigFile() {
|
|||||||
result := suite.dpnd.recorder.String()
|
result := suite.dpnd.recorder.String()
|
||||||
t.Log("backup results", result)
|
t.Log("backup results", result)
|
||||||
|
|
||||||
// as an offhand check: the result should contain the m365 user id
|
// As an offhand check: the result should contain the m365 user's email.
|
||||||
assert.Contains(t, result, suite.its.user.ID)
|
assert.Contains(
|
||||||
|
t,
|
||||||
|
strings.ToLower(result),
|
||||||
|
strings.ToLower(suite.m365.User.Provider.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AWS flags
|
// AWS flags
|
||||||
@ -296,7 +306,7 @@ func (suite *BackupExchangeE2ESuite) TestBackupCreateExchange_badAWSFlags() {
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "exchange",
|
"backup", "create", "exchange",
|
||||||
"--user", suite.its.user.ID,
|
"--user", suite.m365.User.ID,
|
||||||
"--aws-access-key", "invalid-value",
|
"--aws-access-key", "invalid-value",
|
||||||
"--aws-secret-access-key", "some-invalid-value")
|
"--aws-secret-access-key", "some-invalid-value")
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
@ -319,7 +329,7 @@ type PreparedBackupExchangeE2ESuite struct {
|
|||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
backupOps map[path.CategoryType]string
|
backupOps map[path.CategoryType]string
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreparedBackupExchangeE2ESuite(t *testing.T) {
|
func TestPreparedBackupExchangeE2ESuite(t *testing.T) {
|
||||||
@ -336,13 +346,13 @@ func (suite *PreparedBackupExchangeE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.ExchangeService)
|
suite.dpnd = prepM365Test(t, ctx, path.ExchangeService)
|
||||||
suite.backupOps = make(map[path.CategoryType]string)
|
suite.backupOps = make(map[path.CategoryType]string)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
users = []string{suite.its.user.ID}
|
users = []string{suite.m365.User.ID}
|
||||||
ins = idname.NewCache(map[string]string{suite.its.user.ID: suite.its.user.ID})
|
ins = idname.NewCache(map[string]string{suite.m365.User.ID: suite.m365.User.ID})
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, set := range []path.CategoryType{email, contacts, events} {
|
for _, set := range []path.CategoryType{email, contacts, events} {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/internal/operations"
|
"github.com/alcionai/corso/src/internal/operations"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/config"
|
"github.com/alcionai/corso/src/pkg/config"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -35,7 +36,7 @@ import (
|
|||||||
type NoBackupGroupsE2ESuite struct {
|
type NoBackupGroupsE2ESuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoBackupGroupsE2ESuite(t *testing.T) {
|
func TestNoBackupGroupsE2ESuite(t *testing.T) {
|
||||||
@ -50,7 +51,7 @@ func (suite *NoBackupGroupsE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.GroupsService)
|
suite.dpnd = prepM365Test(t, ctx, path.GroupsService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +90,7 @@ func (suite *NoBackupGroupsE2ESuite) TestGroupsBackupListCmd_noBackups() {
|
|||||||
type BackupGroupsE2ESuite struct {
|
type BackupGroupsE2ESuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupGroupsE2ESuite(t *testing.T) {
|
func TestBackupGroupsE2ESuite(t *testing.T) {
|
||||||
@ -104,7 +105,7 @@ func (suite *BackupGroupsE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.GroupsService)
|
suite.dpnd = prepM365Test(t, ctx, path.GroupsService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +114,8 @@ func (suite *BackupGroupsE2ESuite) TestGroupsBackupCmd_channelMessages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupGroupsE2ESuite) TestGroupsBackupCmd_conversations() {
|
func (suite *BackupGroupsE2ESuite) TestGroupsBackupCmd_conversations() {
|
||||||
|
// skip
|
||||||
|
suite.T().Skip("CorsoCITeam group mailbox backup is broken")
|
||||||
runGroupsBackupCategoryTest(suite, flags.DataConversations)
|
runGroupsBackupCategoryTest(suite, flags.DataConversations)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +137,7 @@ func runGroupsBackupCategoryTest(suite *BackupGroupsE2ESuite, category string) {
|
|||||||
cmd, ctx := buildGroupsBackupCmd(
|
cmd, ctx := buildGroupsBackupCmd(
|
||||||
ctx,
|
ctx,
|
||||||
suite.dpnd.configFilePath,
|
suite.dpnd.configFilePath,
|
||||||
suite.its.group.ID,
|
suite.m365.Group.ID,
|
||||||
category,
|
category,
|
||||||
&recorder)
|
&recorder)
|
||||||
|
|
||||||
@ -202,7 +205,7 @@ func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_badAzureClientIDFlag()
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "groups",
|
"backup", "create", "groups",
|
||||||
"--group", suite.its.group.ID,
|
"--group", suite.m365.Group.ID,
|
||||||
"--azure-client-id", "invalid-value")
|
"--azure-client-id", "invalid-value")
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
@ -216,6 +219,9 @@ func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_badAzureClientIDFlag()
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_fromConfigFile() {
|
func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_fromConfigFile() {
|
||||||
|
// Skip
|
||||||
|
suite.T().Skip("CorsoCITeam group mailbox backup is broken")
|
||||||
|
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
ctx = config.SetViper(ctx, suite.dpnd.vpr)
|
||||||
@ -226,7 +232,7 @@ func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_fromConfigFile() {
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "groups",
|
"backup", "create", "groups",
|
||||||
"--group", suite.its.group.ID,
|
"--group", suite.m365.Group.ID,
|
||||||
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
@ -250,7 +256,7 @@ func (suite *BackupGroupsE2ESuite) TestBackupCreateGroups_badAWSFlags() {
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "groups",
|
"backup", "create", "groups",
|
||||||
"--group", suite.its.group.ID,
|
"--group", suite.m365.Group.ID,
|
||||||
"--aws-access-key", "invalid-value",
|
"--aws-access-key", "invalid-value",
|
||||||
"--aws-secret-access-key", "some-invalid-value")
|
"--aws-secret-access-key", "some-invalid-value")
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
@ -273,7 +279,7 @@ type PreparedBackupGroupsE2ESuite struct {
|
|||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
backupOps map[path.CategoryType]string
|
backupOps map[path.CategoryType]string
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreparedBackupGroupsE2ESuite(t *testing.T) {
|
func TestPreparedBackupGroupsE2ESuite(t *testing.T) {
|
||||||
@ -290,16 +296,19 @@ func (suite *PreparedBackupGroupsE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.GroupsService)
|
suite.dpnd = prepM365Test(t, ctx, path.GroupsService)
|
||||||
suite.backupOps = make(map[path.CategoryType]string)
|
suite.backupOps = make(map[path.CategoryType]string)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
groups = []string{suite.its.group.ID}
|
groups = []string{suite.m365.Group.ID}
|
||||||
ins = idname.NewCache(map[string]string{suite.its.group.ID: suite.its.group.ID})
|
ins = idname.NewCache(map[string]string{suite.m365.Group.ID: suite.m365.Group.ID})
|
||||||
cats = []path.CategoryType{
|
cats = []path.CategoryType{
|
||||||
path.ChannelMessagesCategory,
|
path.ChannelMessagesCategory,
|
||||||
path.ConversationPostsCategory,
|
// TODO(pandeyabs): CorsoCITeam group mailbox backup is currently broken because of invalid
|
||||||
|
// odata.NextLink which causes an infinite loop during paging. Disabling conversations tests while
|
||||||
|
// we go fix the group mailbox.
|
||||||
|
// path.ConversationPostsCategory,
|
||||||
path.LibrariesCategory,
|
path.LibrariesCategory,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -453,6 +462,8 @@ func (suite *PreparedBackupGroupsE2ESuite) TestGroupsDetailsCmd_channelMessages(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PreparedBackupGroupsE2ESuite) TestGroupsDetailsCmd_conversations() {
|
func (suite *PreparedBackupGroupsE2ESuite) TestGroupsDetailsCmd_conversations() {
|
||||||
|
// skip
|
||||||
|
suite.T().Skip("CorsoCITeam group mailbox backup is broken")
|
||||||
runGroupsDetailsCmdTest(suite, path.ConversationPostsCategory)
|
runGroupsDetailsCmdTest(suite, path.ConversationPostsCategory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,141 +14,16 @@ import (
|
|||||||
"github.com/alcionai/corso/src/cli/flags"
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
"github.com/alcionai/corso/src/cli/print"
|
"github.com/alcionai/corso/src/cli/print"
|
||||||
cliTD "github.com/alcionai/corso/src/cli/testdata"
|
cliTD "github.com/alcionai/corso/src/cli/testdata"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/config"
|
"github.com/alcionai/corso/src/pkg/config"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/repository"
|
"github.com/alcionai/corso/src/pkg/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
|
||||||
"github.com/alcionai/corso/src/pkg/storage"
|
"github.com/alcionai/corso/src/pkg/storage"
|
||||||
"github.com/alcionai/corso/src/pkg/storage/testdata"
|
"github.com/alcionai/corso/src/pkg/storage/testdata"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Gockable client
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// GockClient produces a new exchange api client that can be
|
|
||||||
// mocked using gock.
|
|
||||||
func gockClient(creds account.M365Config, counter *count.Bus) (api.Client, error) {
|
|
||||||
s, err := graph.NewGockService(creds, counter)
|
|
||||||
if err != nil {
|
|
||||||
return api.Client{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
li, err := graph.NewGockService(creds, counter, graph.NoTimeout())
|
|
||||||
if err != nil {
|
|
||||||
return api.Client{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return api.Client{
|
|
||||||
Credentials: creds,
|
|
||||||
Stable: s,
|
|
||||||
LargeItem: li,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Suite Setup
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type ids struct {
|
|
||||||
ID string
|
|
||||||
DriveID string
|
|
||||||
DriveRootFolderID string
|
|
||||||
}
|
|
||||||
|
|
||||||
type intgTesterSetup struct {
|
|
||||||
acct account.Account
|
|
||||||
ac api.Client
|
|
||||||
gockAC api.Client
|
|
||||||
user ids
|
|
||||||
site ids
|
|
||||||
group ids
|
|
||||||
team ids
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIntegrationTesterSetup(t *testing.T) intgTesterSetup {
|
|
||||||
its := intgTesterSetup{}
|
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
|
||||||
|
|
||||||
its.acct = tconfig.NewM365Account(t)
|
|
||||||
creds, err := its.acct.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
its.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
its.gockAC, err = gockClient(creds, count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
// user drive
|
|
||||||
|
|
||||||
uids := ids{}
|
|
||||||
|
|
||||||
uids.ID = tconfig.M365UserID(t)
|
|
||||||
|
|
||||||
userDrive, err := its.ac.Users().GetDefaultDrive(ctx, uids.ID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
uids.DriveID = ptr.Val(userDrive.GetId())
|
|
||||||
|
|
||||||
userDriveRootFolder, err := its.ac.Drives().GetRootFolder(ctx, uids.DriveID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
uids.DriveRootFolderID = ptr.Val(userDriveRootFolder.GetId())
|
|
||||||
|
|
||||||
its.user = uids
|
|
||||||
|
|
||||||
// site
|
|
||||||
|
|
||||||
sids := ids{}
|
|
||||||
|
|
||||||
sids.ID = tconfig.M365SiteID(t)
|
|
||||||
|
|
||||||
siteDrive, err := its.ac.Sites().GetDefaultDrive(ctx, sids.ID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
sids.DriveID = ptr.Val(siteDrive.GetId())
|
|
||||||
|
|
||||||
siteDriveRootFolder, err := its.ac.Drives().GetRootFolder(ctx, sids.DriveID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
sids.DriveRootFolderID = ptr.Val(siteDriveRootFolder.GetId())
|
|
||||||
|
|
||||||
its.site = sids
|
|
||||||
|
|
||||||
// group
|
|
||||||
|
|
||||||
gids := ids{}
|
|
||||||
|
|
||||||
// use of the TeamID is intentional here, so that we are assured
|
|
||||||
// the group has full usage of the teams api.
|
|
||||||
gids.ID = tconfig.M365TeamID(t)
|
|
||||||
|
|
||||||
its.group = gids
|
|
||||||
|
|
||||||
// team
|
|
||||||
|
|
||||||
tids := ids{}
|
|
||||||
tids.ID = tconfig.M365TeamID(t)
|
|
||||||
its.team = tids
|
|
||||||
|
|
||||||
return its
|
|
||||||
}
|
|
||||||
|
|
||||||
type dependencies struct {
|
type dependencies struct {
|
||||||
st storage.Storage
|
st storage.Storage
|
||||||
repo repository.Repositoryer
|
repo repository.Repositoryer
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/internal/operations"
|
"github.com/alcionai/corso/src/internal/operations"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/config"
|
"github.com/alcionai/corso/src/pkg/config"
|
||||||
@ -89,7 +90,7 @@ func (suite *NoBackupSharePointE2ESuite) TestSharePointBackupListCmd_empty() {
|
|||||||
type BackupSharepointE2ESuite struct {
|
type BackupSharepointE2ESuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupSharepointE2ESuite(t *testing.T) {
|
func TestBackupSharepointE2ESuite(t *testing.T) {
|
||||||
@ -104,7 +105,7 @@ func (suite *BackupSharepointE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.SharePointService)
|
suite.dpnd = prepM365Test(t, ctx, path.SharePointService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ func runSharepointBackupCategoryTest(suite *BackupSharepointE2ESuite, category s
|
|||||||
cmd, ctx := buildSharepointBackupCmd(
|
cmd, ctx := buildSharepointBackupCmd(
|
||||||
ctx,
|
ctx,
|
||||||
suite.dpnd.configFilePath,
|
suite.dpnd.configFilePath,
|
||||||
suite.its.site.ID,
|
suite.m365.Site.ID,
|
||||||
category,
|
category,
|
||||||
&recorder)
|
&recorder)
|
||||||
|
|
||||||
@ -187,7 +188,7 @@ type PreparedBackupSharepointE2ESuite struct {
|
|||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
backupOps map[path.CategoryType]string
|
backupOps map[path.CategoryType]string
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreparedBackupSharepointE2ESuite(t *testing.T) {
|
func TestPreparedBackupSharepointE2ESuite(t *testing.T) {
|
||||||
@ -204,13 +205,13 @@ func (suite *PreparedBackupSharepointE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.SharePointService)
|
suite.dpnd = prepM365Test(t, ctx, path.SharePointService)
|
||||||
suite.backupOps = make(map[path.CategoryType]string)
|
suite.backupOps = make(map[path.CategoryType]string)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sites = []string{suite.its.site.ID}
|
sites = []string{suite.m365.Site.ID}
|
||||||
ins = idname.NewCache(map[string]string{suite.its.site.ID: suite.its.site.ID})
|
ins = idname.NewCache(map[string]string{suite.m365.Site.ID: suite.m365.Site.ID})
|
||||||
cats = []path.CategoryType{
|
cats = []path.CategoryType{
|
||||||
path.ListsCategory,
|
path.ListsCategory,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/internal/operations"
|
"github.com/alcionai/corso/src/internal/operations"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/config"
|
"github.com/alcionai/corso/src/pkg/config"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -35,7 +36,7 @@ import (
|
|||||||
type NoBackupTeamsChatsE2ESuite struct {
|
type NoBackupTeamsChatsE2ESuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoBackupTeamsChatsE2ESuite(t *testing.T) {
|
func TestNoBackupTeamsChatsE2ESuite(t *testing.T) {
|
||||||
@ -51,7 +52,7 @@ func (suite *NoBackupTeamsChatsE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ func (suite *NoBackupTeamsChatsE2ESuite) TestTeamsChatsBackupListCmd_noBackups()
|
|||||||
type BackupTeamsChatsE2ESuite struct {
|
type BackupTeamsChatsE2ESuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupTeamsChatsE2ESuite(t *testing.T) {
|
func TestBackupTeamsChatsE2ESuite(t *testing.T) {
|
||||||
@ -106,7 +107,7 @@ func (suite *BackupTeamsChatsE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ func runTeamsChatsBackupCategoryTest(suite *BackupTeamsChatsE2ESuite, category s
|
|||||||
cmd, ctx := buildTeamsChatsBackupCmd(
|
cmd, ctx := buildTeamsChatsBackupCmd(
|
||||||
ctx,
|
ctx,
|
||||||
suite.dpnd.configFilePath,
|
suite.dpnd.configFilePath,
|
||||||
suite.its.user.ID,
|
suite.m365.User.ID,
|
||||||
category,
|
category,
|
||||||
&recorder)
|
&recorder)
|
||||||
|
|
||||||
@ -188,7 +189,7 @@ func (suite *BackupTeamsChatsE2ESuite) TestBackupCreateTeamsChats_badAzureClient
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "chats",
|
"backup", "create", "chats",
|
||||||
"--teamschat", suite.its.user.ID,
|
"--teamschat", suite.m365.User.ID,
|
||||||
"--azure-client-id", "invalid-value")
|
"--azure-client-id", "invalid-value")
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
@ -212,7 +213,7 @@ func (suite *BackupTeamsChatsE2ESuite) TestBackupCreateTeamsChats_fromConfigFile
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "chats",
|
"backup", "create", "chats",
|
||||||
"--teamschat", suite.its.user.ID,
|
"--teamschat", suite.m365.User.ID,
|
||||||
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
"--"+flags.ConfigFileFN, suite.dpnd.configFilePath)
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
|
|
||||||
@ -236,7 +237,7 @@ func (suite *BackupTeamsChatsE2ESuite) TestBackupCreateTeamsChats_badAWSFlags()
|
|||||||
|
|
||||||
cmd := cliTD.StubRootCmd(
|
cmd := cliTD.StubRootCmd(
|
||||||
"backup", "create", "chats",
|
"backup", "create", "chats",
|
||||||
"--teamschat", suite.its.user.ID,
|
"--teamschat", suite.m365.User.ID,
|
||||||
"--aws-access-key", "invalid-value",
|
"--aws-access-key", "invalid-value",
|
||||||
"--aws-secret-access-key", "some-invalid-value")
|
"--aws-secret-access-key", "some-invalid-value")
|
||||||
cli.BuildCommandTree(cmd)
|
cli.BuildCommandTree(cmd)
|
||||||
@ -259,7 +260,7 @@ type PreparedBackupTeamsChatsE2ESuite struct {
|
|||||||
tester.Suite
|
tester.Suite
|
||||||
dpnd dependencies
|
dpnd dependencies
|
||||||
backupOps map[path.CategoryType]string
|
backupOps map[path.CategoryType]string
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreparedBackupTeamsChatsE2ESuite(t *testing.T) {
|
func TestPreparedBackupTeamsChatsE2ESuite(t *testing.T) {
|
||||||
@ -277,13 +278,13 @@ func (suite *PreparedBackupTeamsChatsE2ESuite) SetupSuite() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.its = newIntegrationTesterSetup(t)
|
suite.m365 = its.GetM365(t)
|
||||||
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
suite.dpnd = prepM365Test(t, ctx, path.TeamsChatsService)
|
||||||
suite.backupOps = make(map[path.CategoryType]string)
|
suite.backupOps = make(map[path.CategoryType]string)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
teamschats = []string{suite.its.user.ID}
|
teamschats = []string{suite.m365.User.ID}
|
||||||
ins = idname.NewCache(map[string]string{suite.its.user.ID: suite.its.user.ID})
|
ins = idname.NewCache(map[string]string{suite.m365.User.ID: suite.m365.User.ID})
|
||||||
cats = []path.CategoryType{
|
cats = []path.CategoryType{
|
||||||
path.ChatsCategory,
|
path.ChatsCategory,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,12 +6,6 @@ Param (
|
|||||||
[Parameter(Mandatory = $False, HelpMessage = "Site for which to delete folders in SharePoint")]
|
[Parameter(Mandatory = $False, HelpMessage = "Site for which to delete folders in SharePoint")]
|
||||||
[String]$Site,
|
[String]$Site,
|
||||||
|
|
||||||
[Parameter(Mandatory = $False, HelpMessage = "Exchange Admin email")]
|
|
||||||
[String]$AdminUser = $ENV:M365_TENANT_ADMIN_USER,
|
|
||||||
|
|
||||||
[Parameter(Mandatory = $False, HelpMessage = "Exchange Admin password")]
|
|
||||||
[String]$AdminPwd = $ENV:M365_TENANT_ADMIN_PASSWORD,
|
|
||||||
|
|
||||||
[Parameter(Mandatory = $False, HelpMessage = "Document library root. Can add multiple comma-separated values")]
|
[Parameter(Mandatory = $False, HelpMessage = "Document library root. Can add multiple comma-separated values")]
|
||||||
[String[]]$LibraryNameList = @(),
|
[String[]]$LibraryNameList = @(),
|
||||||
|
|
||||||
@ -22,7 +16,16 @@ Param (
|
|||||||
[String[]]$FolderPrefixPurgeList,
|
[String[]]$FolderPrefixPurgeList,
|
||||||
|
|
||||||
[Parameter(Mandatory = $False, HelpMessage = "Delete document libraries with this prefix")]
|
[Parameter(Mandatory = $False, HelpMessage = "Delete document libraries with this prefix")]
|
||||||
[String[]]$LibraryPrefixDeleteList = @()
|
[String[]]$LibraryPrefixDeleteList = @(),
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $False, HelpMessage = "Tenant domain")]
|
||||||
|
[String]$TenantDomain = $ENV:TENANT_DOMAIN,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $False, HelpMessage = "Azure ClientId")]
|
||||||
|
[String]$ClientId = $ENV:AZURE_CLIENT_ID,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $False, HelpMessage = "Azure AppCert")]
|
||||||
|
[String]$AppCert = $ENV:AZURE_APP_CERT
|
||||||
)
|
)
|
||||||
|
|
||||||
Set-StrictMode -Version 2.0
|
Set-StrictMode -Version 2.0
|
||||||
@ -108,6 +111,7 @@ function Purge-Library {
|
|||||||
$foldersToPurge = @()
|
$foldersToPurge = @()
|
||||||
$folders = Get-PnPFolderItem -FolderSiteRelativeUrl $LibraryName -ItemType Folder
|
$folders = Get-PnPFolderItem -FolderSiteRelativeUrl $LibraryName -ItemType Folder
|
||||||
|
|
||||||
|
Write-Host "`nFolders: $folders"
|
||||||
foreach ($f in $folders) {
|
foreach ($f in $folders) {
|
||||||
$folderName = $f.Name
|
$folderName = $f.Name
|
||||||
$createTime = Get-TimestampFromFolderName -Folder $f
|
$createTime = Get-TimestampFromFolderName -Folder $f
|
||||||
@ -209,8 +213,8 @@ if (-not (Get-Module -ListAvailable -Name PnP.PowerShell)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ([string]::IsNullOrEmpty($AdminUser) -or [string]::IsNullOrEmpty($AdminPwd)) {
|
if ([string]::IsNullOrEmpty($ClientId) -or [string]::IsNullOrEmpty($AppCert)) {
|
||||||
Write-Host "Admin user name and password required as arguments or environment variables."
|
Write-Host "ClientId and AppCert required as arguments or environment variables."
|
||||||
Exit
|
Exit
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,12 +255,8 @@ else {
|
|||||||
Exit
|
Exit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$password = convertto-securestring -String $AdminPwd -AsPlainText -Force
|
|
||||||
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AdminUser, $password
|
|
||||||
|
|
||||||
Write-Host "`nAuthenticating and connecting to $SiteUrl"
|
Write-Host "`nAuthenticating and connecting to $SiteUrl"
|
||||||
Connect-PnPOnline -Url $siteUrl -Credential $cred
|
Connect-PnPOnline -Url $siteUrl -ClientId $ClientId -CertificateBase64Encoded $AppCert -Tenant $TenantDomain
|
||||||
Write-Host "Connected to $siteUrl`n"
|
Write-Host "Connected to $siteUrl`n"
|
||||||
|
|
||||||
# ensure that there are no unexpanded entries in the list of parameters
|
# ensure that there are no unexpanded entries in the list of parameters
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/cmd/sanity_test/common"
|
"github.com/alcionai/corso/src/cmd/sanity_test/common"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
@ -20,19 +21,20 @@ const (
|
|||||||
// this increases the chance that we'll run into a race collision with
|
// this increases the chance that we'll run into a race collision with
|
||||||
// the cleanup script. Sometimes that's okay (deleting old data that
|
// the cleanup script. Sometimes that's okay (deleting old data that
|
||||||
// isn't scrutinized in the test), other times it's not. We mark whether
|
// isn't scrutinized in the test), other times it's not. We mark whether
|
||||||
// that's okay to do or not by specifying the folder that's being
|
// that's okay to do or not by specifying the folders being
|
||||||
// scrutinized for the test. Any errors within that folder should cause
|
// scrutinized for the test. Any errors within those folders should cause
|
||||||
// a fatal exit. Errors outside of that folder get ignored.
|
// a fatal exit. Errors outside of those folders get ignored.
|
||||||
//
|
//
|
||||||
// since we're using folder names, requireNoErrorsWithinFolderName will
|
// since we're using folder names, mustPopulateFolders will
|
||||||
// work best (ie: have the fewest collisions/side-effects) if the folder
|
// work best (ie: have the fewest collisions/side-effects) if the folder
|
||||||
// name is very specific. Standard sanity tests should include timestamps,
|
// names are very specific. Standard sanity tests should include timestamps,
|
||||||
// which should help ensure that. Be warned if you try to use it with
|
// which should help ensure that. Be warned if you try to use it with
|
||||||
// a more generic name: unintended effects could occur.
|
// a more generic name: unintended effects could occur.
|
||||||
func populateSanitree(
|
func populateSanitree(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
ac api.Client,
|
ac api.Client,
|
||||||
driveID, requireNoErrorsWithinFolderName string,
|
driveID string,
|
||||||
|
mustPopulateFolders []string,
|
||||||
) *common.Sanitree[models.DriveItemable, models.DriveItemable] {
|
) *common.Sanitree[models.DriveItemable, models.DriveItemable] {
|
||||||
common.Infof(ctx, "building sanitree for drive: %s", driveID)
|
common.Infof(ctx, "building sanitree for drive: %s", driveID)
|
||||||
|
|
||||||
@ -56,8 +58,8 @@ func populateSanitree(
|
|||||||
ac,
|
ac,
|
||||||
driveID,
|
driveID,
|
||||||
stree.Name+"/",
|
stree.Name+"/",
|
||||||
requireNoErrorsWithinFolderName,
|
mustPopulateFolders,
|
||||||
rootName == requireNoErrorsWithinFolderName,
|
slices.Contains(mustPopulateFolders, rootName),
|
||||||
stree)
|
stree)
|
||||||
|
|
||||||
return stree
|
return stree
|
||||||
@ -66,7 +68,9 @@ func populateSanitree(
|
|||||||
func recursivelyBuildTree(
|
func recursivelyBuildTree(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
ac api.Client,
|
ac api.Client,
|
||||||
driveID, location, requireNoErrorsWithinFolderName string,
|
driveID string,
|
||||||
|
location string,
|
||||||
|
mustPopulateFolders []string,
|
||||||
isChildOfFolderRequiringNoErrors bool,
|
isChildOfFolderRequiringNoErrors bool,
|
||||||
stree *common.Sanitree[models.DriveItemable, models.DriveItemable],
|
stree *common.Sanitree[models.DriveItemable, models.DriveItemable],
|
||||||
) {
|
) {
|
||||||
@ -80,9 +84,9 @@ func recursivelyBuildTree(
|
|||||||
|
|
||||||
common.Infof(
|
common.Infof(
|
||||||
ctx,
|
ctx,
|
||||||
"ignoring error getting children in directory %q because it is not within directory %q\nerror: %s\n%+v",
|
"ignoring error getting children in directory %q because it is not within directory set %v\nerror: %s\n%+v",
|
||||||
location,
|
location,
|
||||||
requireNoErrorsWithinFolderName,
|
mustPopulateFolders,
|
||||||
err.Error(),
|
err.Error(),
|
||||||
clues.ToCore(err))
|
clues.ToCore(err))
|
||||||
|
|
||||||
@ -99,11 +103,12 @@ func recursivelyBuildTree(
|
|||||||
// currently we don't restore blank folders.
|
// currently we don't restore blank folders.
|
||||||
// skip permission check for empty folders
|
// skip permission check for empty folders
|
||||||
if ptr.Val(driveItem.GetFolder().GetChildCount()) == 0 {
|
if ptr.Val(driveItem.GetFolder().GetChildCount()) == 0 {
|
||||||
common.Infof(ctx, "skipped empty folder: %s/%s", location, itemName)
|
common.Infof(ctx, "skipped empty folder: %s%s", location, itemName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cannotAllowErrors := isChildOfFolderRequiringNoErrors || itemName == requireNoErrorsWithinFolderName
|
cannotAllowErrors := isChildOfFolderRequiringNoErrors ||
|
||||||
|
slices.Contains(mustPopulateFolders, itemName)
|
||||||
|
|
||||||
branch := &common.Sanitree[models.DriveItemable, models.DriveItemable]{
|
branch := &common.Sanitree[models.DriveItemable, models.DriveItemable]{
|
||||||
Parent: stree,
|
Parent: stree,
|
||||||
@ -124,7 +129,7 @@ func recursivelyBuildTree(
|
|||||||
ac,
|
ac,
|
||||||
driveID,
|
driveID,
|
||||||
location+branch.Name+"/",
|
location+branch.Name+"/",
|
||||||
requireNoErrorsWithinFolderName,
|
mustPopulateFolders,
|
||||||
cannotAllowErrors,
|
cannotAllowErrors,
|
||||||
branch)
|
branch)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ func CheckExport(
|
|||||||
ctx,
|
ctx,
|
||||||
ac,
|
ac,
|
||||||
driveID,
|
driveID,
|
||||||
envs.RestoreContainer)
|
[]string{envs.SourceContainer})
|
||||||
|
|
||||||
sourceTree, ok := root.Children[envs.SourceContainer]
|
sourceTree, ok := root.Children[envs.SourceContainer]
|
||||||
common.Assert(
|
common.Assert(
|
||||||
|
|||||||
@ -45,7 +45,14 @@ func CheckRestoration(
|
|||||||
"drive_id", driveID,
|
"drive_id", driveID,
|
||||||
"drive_name", driveName)
|
"drive_name", driveName)
|
||||||
|
|
||||||
root := populateSanitree(ctx, ac, driveID, envs.RestoreContainer)
|
root := populateSanitree(
|
||||||
|
ctx,
|
||||||
|
ac,
|
||||||
|
driveID,
|
||||||
|
[]string{
|
||||||
|
envs.SourceContainer,
|
||||||
|
envs.RestoreContainer,
|
||||||
|
})
|
||||||
|
|
||||||
sourceTree, ok := root.Children[envs.SourceContainer]
|
sourceTree, ok := root.Children[envs.SourceContainer]
|
||||||
common.Assert(
|
common.Assert(
|
||||||
|
|||||||
@ -3,7 +3,7 @@ module github.com/alcionai/corso/src
|
|||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/kopia/kopia => github.com/alcionai/kopia v0.12.2-0.20240116215733-ec3d100029fe
|
github.com/kopia/kopia => github.com/alcionai/kopia v0.12.2-0.20240322180947-41471159a0a4
|
||||||
|
|
||||||
// Alcion fork removes the validation of email addresses as we might get incomplete email addresses
|
// Alcion fork removes the validation of email addresses as we might get incomplete email addresses
|
||||||
github.com/xhit/go-simple-mail/v2 v2.16.0 => github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a
|
github.com/xhit/go-simple-mail/v2 v2.16.0 => github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a
|
||||||
@ -121,7 +121,7 @@ require (
|
|||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/microsoft/kiota-serialization-text-go v1.0.0
|
github.com/microsoft/kiota-serialization-text-go v1.0.0
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/minio/minio-go/v7 v7.0.66
|
github.com/minio/minio-go/v7 v7.0.67
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
|||||||
@ -23,8 +23,8 @@ github.com/alcionai/clues v0.0.0-20240125221452-9fc7746dd20c h1:QtARFaqYKtGjmEej
|
|||||||
github.com/alcionai/clues v0.0.0-20240125221452-9fc7746dd20c/go.mod h1:1YJwJy3W6GGsC2UiDAEWABUjgvT8OZHjKs8OoaXeKbw=
|
github.com/alcionai/clues v0.0.0-20240125221452-9fc7746dd20c/go.mod h1:1YJwJy3W6GGsC2UiDAEWABUjgvT8OZHjKs8OoaXeKbw=
|
||||||
github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a h1:4nhM0NM1qtUT1s55rQ+D0Xw1Re5mIU9/crjEl6KdE+k=
|
github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a h1:4nhM0NM1qtUT1s55rQ+D0Xw1Re5mIU9/crjEl6KdE+k=
|
||||||
github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
github.com/alcionai/go-simple-mail/v2 v2.0.0-20231220071811-c70ebcd9a41a/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
||||||
github.com/alcionai/kopia v0.12.2-0.20240116215733-ec3d100029fe h1:nLS5pxhm04Jz4+qeipNlxdyPGxqNWpBu8UGkRYpWoIw=
|
github.com/alcionai/kopia v0.12.2-0.20240322180947-41471159a0a4 h1:3YZ70H3mkUgwiHLiNvukrqh2awRgfl1RAkbV0IoUqqk=
|
||||||
github.com/alcionai/kopia v0.12.2-0.20240116215733-ec3d100029fe/go.mod h1:QFRSOUQzZfKE3hKVBHP7hxOn5WyrEmdBtfN5wkib/eA=
|
github.com/alcionai/kopia v0.12.2-0.20240322180947-41471159a0a4/go.mod h1:QFRSOUQzZfKE3hKVBHP7hxOn5WyrEmdBtfN5wkib/eA=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
@ -219,8 +219,8 @@ github.com/microsoftgraph/msgraph-sdk-go-core v1.0.1 h1:uq4qZD8VXLiNZY0t4NoRpLDo
|
|||||||
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.1/go.mod h1:HUITyuFN556+0QZ/IVfH5K4FyJM7kllV6ExKi2ImKhE=
|
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.1/go.mod h1:HUITyuFN556+0QZ/IVfH5K4FyJM7kllV6ExKi2ImKhE=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
|
github.com/minio/minio-go/v7 v7.0.67 h1:BeBvZWAS+kRJm1vGTMJYVjKUNoo0FoEt/wUWdUtfmh8=
|
||||||
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
|
github.com/minio/minio-go/v7 v7.0.67/go.mod h1:+UXocnUeZ3wHvVh5s95gcrA4YjMIbccT6ubB+1m054A=
|
||||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/pkg/dttm"
|
"github.com/alcionai/corso/src/pkg/dttm"
|
||||||
"github.com/alcionai/corso/src/pkg/export"
|
"github.com/alcionai/corso/src/pkg/export"
|
||||||
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -56,12 +57,22 @@ func ZipExportCollection(
|
|||||||
defer wr.Close()
|
defer wr.Close()
|
||||||
|
|
||||||
buf := make([]byte, ZipCopyBufferSize)
|
buf := make([]byte, ZipCopyBufferSize)
|
||||||
|
counted := 0
|
||||||
|
log := logger.Ctx(ctx).
|
||||||
|
With("collection_count", len(expCollections))
|
||||||
|
|
||||||
for _, ec := range expCollections {
|
for _, ec := range expCollections {
|
||||||
folder := ec.BasePath()
|
folder := ec.BasePath()
|
||||||
items := ec.Items(ctx)
|
items := ec.Items(ctx)
|
||||||
|
|
||||||
for item := range items {
|
for item := range items {
|
||||||
|
counted++
|
||||||
|
|
||||||
|
// Log every 1000 items that are processed
|
||||||
|
if counted%1000 == 0 {
|
||||||
|
log.Infow("progress zipping export items", "count_items", counted)
|
||||||
|
}
|
||||||
|
|
||||||
err := item.Error
|
err := item.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writer.CloseWithError(clues.Wrap(err, "getting export item").With("id", item.ID))
|
writer.CloseWithError(clues.Wrap(err, "getting export item").With("id", item.ID))
|
||||||
@ -88,8 +99,12 @@ func ZipExportCollection(
|
|||||||
writer.CloseWithError(clues.Wrap(err, "writing zip entry").With("name", name).With("id", item.ID))
|
writer.CloseWithError(clues.Wrap(err, "writing zip entry").With("name", name).With("id", item.ID))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.Body.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infow("completed zipping export items", "count_items", counted)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return zipCollection{reader}, nil
|
return zipCollection{reader}, nil
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
package jwt
|
package jwt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
jwt "github.com/golang-jwt/jwt/v5"
|
jwt "github.com/golang-jwt/jwt/v5"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsJWTExpired checks if the JWT token is past expiry by analyzing the
|
// IsJWTExpired checks if the JWT token is past expiry by analyzing the
|
||||||
@ -37,3 +40,51 @@ func IsJWTExpired(
|
|||||||
|
|
||||||
return expired, nil
|
return expired, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetJWTLifetime returns the issued at(iat) and expiration time(exp) claims
|
||||||
|
// present in the JWT token. These are optional claims and may not be present
|
||||||
|
// in the token. Absence is not reported as an error.
|
||||||
|
//
|
||||||
|
// An error is returned if the supplied token is malformed. Times are returned
|
||||||
|
// in UTC to have parity with graph responses.
|
||||||
|
func GetJWTLifetime(
|
||||||
|
ctx context.Context,
|
||||||
|
rawToken string,
|
||||||
|
) (time.Time, time.Time, error) {
|
||||||
|
var (
|
||||||
|
issuedAt time.Time
|
||||||
|
expiresAt time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
p := jwt.NewParser()
|
||||||
|
|
||||||
|
token, _, err := p.ParseUnverified(rawToken, &jwt.RegisteredClaims{})
|
||||||
|
if err != nil {
|
||||||
|
logger.CtxErr(ctx, err).Debug("parsing jwt token")
|
||||||
|
return time.Time{}, time.Time{}, clues.Wrap(err, "invalid jwt")
|
||||||
|
}
|
||||||
|
|
||||||
|
exp, err := token.Claims.GetExpirationTime()
|
||||||
|
if err != nil {
|
||||||
|
logger.CtxErr(ctx, err).Debug("extracting exp claim")
|
||||||
|
return time.Time{}, time.Time{}, clues.Wrap(err, "getting token expiry time")
|
||||||
|
}
|
||||||
|
|
||||||
|
iat, err := token.Claims.GetIssuedAt()
|
||||||
|
if err != nil {
|
||||||
|
logger.CtxErr(ctx, err).Debug("extracting iat claim")
|
||||||
|
return time.Time{}, time.Time{}, clues.Wrap(err, "getting token issued at time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absence of iat or exp claims is not reported as an error by jwt library as these
|
||||||
|
// are optional as per spec.
|
||||||
|
if iat != nil {
|
||||||
|
issuedAt = iat.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp != nil {
|
||||||
|
expiresAt = exp.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
return issuedAt, expiresAt, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -113,3 +113,134 @@ func (suite *JWTUnitSuite) TestIsJWTExpired() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *JWTUnitSuite) TestGetJWTLifetime() {
|
||||||
|
// Set of time values to be used in the tests.
|
||||||
|
// Truncate to seconds for comparisons since jwt tokens have second
|
||||||
|
// level precision.
|
||||||
|
idToTime := map[string]time.Time{
|
||||||
|
"T0": time.Now().UTC().Add(-time.Hour).Truncate(time.Second),
|
||||||
|
"T1": time.Now().UTC().Truncate(time.Second),
|
||||||
|
"T2": time.Now().UTC().Add(time.Hour).Truncate(time.Second),
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
getToken func() (string, error)
|
||||||
|
expectFunc func(t *testing.T, iat time.Time, exp time.Time)
|
||||||
|
expectErr assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "alive token",
|
||||||
|
getToken: func() (string, error) {
|
||||||
|
return createJWTToken(
|
||||||
|
jwt.RegisteredClaims{
|
||||||
|
IssuedAt: jwt.NewNumericDate(idToTime["T0"]),
|
||||||
|
ExpiresAt: jwt.NewNumericDate(idToTime["T1"]),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
|
||||||
|
assert.Equal(t, idToTime["T0"], iat)
|
||||||
|
assert.Equal(t, idToTime["T1"], exp)
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
// Test with a token which is not generated using the go-jwt lib.
|
||||||
|
// This is a long lived token which is valid for 100 years.
|
||||||
|
{
|
||||||
|
name: "alive raw token with iat and exp claims",
|
||||||
|
getToken: func() (string, error) {
|
||||||
|
return rawToken, nil
|
||||||
|
},
|
||||||
|
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
|
||||||
|
assert.Less(t, iat, time.Now(), "iat should be in the past")
|
||||||
|
assert.Greater(t, exp, time.Now(), "exp should be in the future")
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
// Regardless of whether the token is expired or not, we should be able to
|
||||||
|
// extract the iat and exp claims from it without error.
|
||||||
|
{
|
||||||
|
name: "expired token",
|
||||||
|
getToken: func() (string, error) {
|
||||||
|
return createJWTToken(
|
||||||
|
jwt.RegisteredClaims{
|
||||||
|
IssuedAt: jwt.NewNumericDate(idToTime["T1"]),
|
||||||
|
ExpiresAt: jwt.NewNumericDate(idToTime["T0"]),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
|
||||||
|
assert.Equal(t, idToTime["T1"], iat)
|
||||||
|
assert.Equal(t, idToTime["T0"], exp)
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing iat claim",
|
||||||
|
getToken: func() (string, error) {
|
||||||
|
return createJWTToken(
|
||||||
|
jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(idToTime["T2"]),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
|
||||||
|
assert.Equal(t, time.Time{}, iat)
|
||||||
|
assert.Equal(t, idToTime["T2"], exp)
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing exp claim",
|
||||||
|
getToken: func() (string, error) {
|
||||||
|
return createJWTToken(
|
||||||
|
jwt.RegisteredClaims{
|
||||||
|
IssuedAt: jwt.NewNumericDate(idToTime["T0"]),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
|
||||||
|
assert.Equal(t, idToTime["T0"], iat)
|
||||||
|
assert.Equal(t, time.Time{}, exp)
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both claims missing",
|
||||||
|
getToken: func() (string, error) {
|
||||||
|
return createJWTToken(jwt.RegisteredClaims{})
|
||||||
|
},
|
||||||
|
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
|
||||||
|
assert.Equal(t, time.Time{}, iat)
|
||||||
|
assert.Equal(t, time.Time{}, exp)
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed token",
|
||||||
|
getToken: func() (string, error) {
|
||||||
|
return "header.claims.signature", nil
|
||||||
|
},
|
||||||
|
expectFunc: func(t *testing.T, iat time.Time, exp time.Time) {
|
||||||
|
assert.Equal(t, time.Time{}, iat)
|
||||||
|
assert.Equal(t, time.Time{}, exp)
|
||||||
|
},
|
||||||
|
expectErr: assert.Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
token, err := test.getToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
iat, exp, err := GetJWTLifetime(ctx, token)
|
||||||
|
test.expectErr(t, err)
|
||||||
|
|
||||||
|
test.expectFunc(t, iat, exp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -59,6 +59,19 @@ func First(vs ...string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FirstIn returns the first entry in the map with a non-zero value
|
||||||
|
// when iterating the provided list of keys.
|
||||||
|
func FirstIn(m map[string]any, keys ...string) string {
|
||||||
|
for _, key := range keys {
|
||||||
|
v, err := AnyValueToString(key, m)
|
||||||
|
if err == nil && len(v) > 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Preview reduces the string to the specified size.
|
// Preview reduces the string to the specified size.
|
||||||
// If the string is longer than the size, the last three
|
// If the string is longer than the size, the last three
|
||||||
// characters are replaced with an ellipsis. Size < 4
|
// characters are replaced with an ellipsis. Size < 4
|
||||||
|
|||||||
@ -118,3 +118,96 @@ func TestGenerateHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFirstIn(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
m map[string]any
|
||||||
|
keys []string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil map",
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty map",
|
||||||
|
m: map[string]any{},
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no match",
|
||||||
|
m: map[string]any{
|
||||||
|
"baz": "baz",
|
||||||
|
},
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no keys",
|
||||||
|
m: map[string]any{
|
||||||
|
"baz": "baz",
|
||||||
|
},
|
||||||
|
keys: []string{},
|
||||||
|
expect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil match",
|
||||||
|
m: map[string]any{
|
||||||
|
"foo": nil,
|
||||||
|
},
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty match",
|
||||||
|
m: map[string]any{
|
||||||
|
"foo": "",
|
||||||
|
},
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "matches first key",
|
||||||
|
m: map[string]any{
|
||||||
|
"foo": "fnords",
|
||||||
|
},
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "fnords",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "matches second key",
|
||||||
|
m: map[string]any{
|
||||||
|
"bar": "smarf",
|
||||||
|
},
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "smarf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "matches second key with nil first match",
|
||||||
|
m: map[string]any{
|
||||||
|
"foo": nil,
|
||||||
|
"bar": "smarf",
|
||||||
|
},
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "smarf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "matches second key with empty first match",
|
||||||
|
m: map[string]any{
|
||||||
|
"foo": "",
|
||||||
|
"bar": "smarf",
|
||||||
|
},
|
||||||
|
keys: []string{"foo", "bar"},
|
||||||
|
expect: "smarf",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := FirstIn(test.m, test.keys...)
|
||||||
|
assert.Equal(t, test.expect, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -143,6 +143,121 @@ func getICalData(ctx context.Context, data models.Messageable) (string, error) {
|
|||||||
return ics.FromEventable(ctx, event)
|
return ics.FromEventable(ctx, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFileAttachment(ctx context.Context, attachment models.Attachmentable) (*mail.File, error) {
|
||||||
|
kind := ptr.Val(attachment.GetContentType())
|
||||||
|
|
||||||
|
bytes, err := attachment.GetBackingStore().Get("contentBytes")
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.WrapWC(ctx, err, "failed to get attachment bytes").
|
||||||
|
With("kind", kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes == nil {
|
||||||
|
// TODO(meain): Handle non file attachments
|
||||||
|
// https://github.com/alcionai/corso/issues/4772
|
||||||
|
logger.Ctx(ctx).
|
||||||
|
With("attachment_id", ptr.Val(attachment.GetId()),
|
||||||
|
"attachment_type", ptr.Val(attachment.GetOdataType())).
|
||||||
|
Info("no contentBytes for attachment")
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bts, ok := bytes.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, clues.WrapWC(ctx, err, "invalid content bytes").
|
||||||
|
With("kind", kind).
|
||||||
|
With("interface_type", fmt.Sprintf("%T", bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ptr.Val(attachment.GetName())
|
||||||
|
if len(name) == 0 {
|
||||||
|
// Graph as of now does not let us create any attachments
|
||||||
|
// without a name, but we have run into instances where we have
|
||||||
|
// see attachments without a name, possibly from old
|
||||||
|
// data. This is for those cases.
|
||||||
|
name = "Unnamed"
|
||||||
|
}
|
||||||
|
|
||||||
|
contentID, err := attachment.GetBackingStore().Get("contentId")
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.WrapWC(ctx, err, "getting content id for attachment").
|
||||||
|
With("kind", kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentID != nil {
|
||||||
|
cids, _ := str.AnyToString(contentID)
|
||||||
|
if len(cids) > 0 {
|
||||||
|
name = cids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mail.File{
|
||||||
|
// cannot use filename as inline attachment will not get mapped properly
|
||||||
|
Name: name,
|
||||||
|
MimeType: kind,
|
||||||
|
Data: bts,
|
||||||
|
Inline: ptr.Val(attachment.GetIsInline()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getItemAttachment(ctx context.Context, attachment models.Attachmentable) (*mail.File, error) {
|
||||||
|
it, err := attachment.GetBackingStore().Get("item")
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.WrapWC(ctx, err, "getting item for attachment").
|
||||||
|
With("attachment_id", ptr.Val(attachment.GetId()))
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ptr.Val(attachment.GetName())
|
||||||
|
if len(name) == 0 {
|
||||||
|
// Graph as of now does not let us create any attachments
|
||||||
|
// without a name, but we have run into instances where we have
|
||||||
|
// see attachments without a name, possibly from old
|
||||||
|
// data. This is for those cases.
|
||||||
|
name = "Unnamed"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch it := it.(type) {
|
||||||
|
case *models.Message:
|
||||||
|
cb, err := FromMessageable(ctx, it)
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.WrapWC(ctx, err, "converting item attachment to eml").
|
||||||
|
With("attachment_id", ptr.Val(attachment.GetId()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mail.File{
|
||||||
|
Name: name,
|
||||||
|
MimeType: "message/rfc822",
|
||||||
|
Data: []byte(cb),
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
logger.Ctx(ctx).
|
||||||
|
With("attachment_id", ptr.Val(attachment.GetId()),
|
||||||
|
"attachment_type", ptr.Val(attachment.GetOdataType())).
|
||||||
|
Info("unknown item attachment type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMailAttachment(ctx context.Context, att models.Attachmentable) (*mail.File, error) {
|
||||||
|
otyp := ptr.Val(att.GetOdataType())
|
||||||
|
|
||||||
|
switch otyp {
|
||||||
|
case "#microsoft.graph.fileAttachment":
|
||||||
|
return getFileAttachment(ctx, att)
|
||||||
|
case "#microsoft.graph.itemAttachment":
|
||||||
|
return getItemAttachment(ctx, att)
|
||||||
|
default:
|
||||||
|
logger.Ctx(ctx).
|
||||||
|
With("attachment_id", ptr.Val(att.GetId()),
|
||||||
|
"attachment_type", otyp).
|
||||||
|
Info("unknown attachment type")
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FromJSON converts a Messageable (as json) to .eml format
|
// FromJSON converts a Messageable (as json) to .eml format
|
||||||
func FromJSON(ctx context.Context, body []byte) (string, error) {
|
func FromJSON(ctx context.Context, body []byte) (string, error) {
|
||||||
ctx = clues.Add(ctx, "body_len", len(body))
|
ctx = clues.Add(ctx, "body_len", len(body))
|
||||||
@ -152,6 +267,11 @@ func FromJSON(ctx context.Context, body []byte) (string, error) {
|
|||||||
return "", clues.WrapWC(ctx, err, "converting to messageble")
|
return "", clues.WrapWC(ctx, err, "converting to messageble")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return FromMessageable(ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a Messageable to .eml format
|
||||||
|
func FromMessageable(ctx context.Context, data models.Messageable) (string, error) {
|
||||||
ctx = clues.Add(ctx, "item_id", ptr.Val(data.GetId()))
|
ctx = clues.Add(ctx, "item_id", ptr.Val(data.GetId()))
|
||||||
|
|
||||||
email := mail.NewMSG()
|
email := mail.NewMSG()
|
||||||
@ -229,54 +349,16 @@ func FromJSON(ctx context.Context, body []byte) (string, error) {
|
|||||||
|
|
||||||
if data.GetAttachments() != nil {
|
if data.GetAttachments() != nil {
|
||||||
for _, attachment := range data.GetAttachments() {
|
for _, attachment := range data.GetAttachments() {
|
||||||
kind := ptr.Val(attachment.GetContentType())
|
att, err := getMailAttachment(ctx, attachment)
|
||||||
|
|
||||||
bytes, err := attachment.GetBackingStore().Get("contentBytes")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", clues.WrapWC(ctx, err, "failed to get attachment bytes").
|
return "", clues.WrapWC(ctx, err, "getting mail attachment")
|
||||||
With("kind", kind)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes == nil {
|
// There are known cases where we just wanna log and
|
||||||
// TODO(meain): Handle non file attachments
|
// ignore instead of erroring out
|
||||||
// https://github.com/alcionai/corso/issues/4772
|
if att != nil {
|
||||||
logger.Ctx(ctx).
|
email.Attach(att)
|
||||||
With("attachment_id", ptr.Val(attachment.GetId()),
|
|
||||||
"attachment_type", ptr.Val(attachment.GetOdataType())).
|
|
||||||
Info("no contentBytes for attachment")
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bts, ok := bytes.([]byte)
|
|
||||||
if !ok {
|
|
||||||
return "", clues.WrapWC(ctx, err, "invalid content bytes").
|
|
||||||
With("kind", kind).
|
|
||||||
With("interface_type", fmt.Sprintf("%T", bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
name := ptr.Val(attachment.GetName())
|
|
||||||
|
|
||||||
contentID, err := attachment.GetBackingStore().Get("contentId")
|
|
||||||
if err != nil {
|
|
||||||
return "", clues.WrapWC(ctx, err, "getting content id for attachment").
|
|
||||||
With("kind", kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
if contentID != nil {
|
|
||||||
cids, _ := str.AnyToString(contentID)
|
|
||||||
if len(cids) > 0 {
|
|
||||||
name = cids
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
email.Attach(&mail.File{
|
|
||||||
// cannot use filename as inline attachment will not get mapped properly
|
|
||||||
Name: name,
|
|
||||||
MimeType: kind,
|
|
||||||
Data: bts,
|
|
||||||
Inline: ptr.Val(attachment.GetIsInline()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,7 +380,7 @@ func FromJSON(ctx context.Context, body []byte) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = email.GetError(); err != nil {
|
if err := email.GetError(); err != nil {
|
||||||
return "", clues.WrapWC(ctx, err, "converting to eml")
|
return "", clues.WrapWC(ctx, err, "converting to eml")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,6 +488,9 @@ func FromJSONPostToEML(
|
|||||||
}
|
}
|
||||||
|
|
||||||
name := ptr.Val(attachment.GetName())
|
name := ptr.Val(attachment.GetName())
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = "Unnamed"
|
||||||
|
}
|
||||||
|
|
||||||
contentID, err := attachment.GetBackingStore().Get("contentId")
|
contentID, err := attachment.GetBackingStore().Get("contentId")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -137,6 +137,11 @@ func (suite *EMLUnitSuite) TestConvert_messageble_to_eml() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *EMLUnitSuite) TestConvert_edge_cases() {
|
func (suite *EMLUnitSuite) TestConvert_edge_cases() {
|
||||||
|
bodies := []string{
|
||||||
|
testdata.EmailWithAttachments,
|
||||||
|
testdata.EmailWithinEmail,
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
transform func(models.Messageable)
|
transform func(models.Messageable)
|
||||||
@ -162,8 +167,47 @@ func (suite *EMLUnitSuite) TestConvert_edge_cases() {
|
|||||||
require.NoError(suite.T(), err, "setting attachment content")
|
require.NoError(suite.T(), err, "setting attachment content")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "attachment without name",
|
||||||
|
transform: func(msg models.Messageable) {
|
||||||
|
attachments := msg.GetAttachments()
|
||||||
|
attachments[1].SetName(ptr.To(""))
|
||||||
|
|
||||||
|
// This test has to be run on a non inline attachment
|
||||||
|
// as inline attachments use contentID instead of name
|
||||||
|
// even when there is a name.
|
||||||
|
assert.False(suite.T(), ptr.Val(attachments[1].GetIsInline()))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "attachment with nil name",
|
||||||
|
transform: func(msg models.Messageable) {
|
||||||
|
attachments := msg.GetAttachments()
|
||||||
|
attachments[1].SetName(nil)
|
||||||
|
|
||||||
|
// This test has to be run on a non inline attachment
|
||||||
|
// as inline attachments use contentID instead of name
|
||||||
|
// even when there is a name.
|
||||||
|
assert.False(suite.T(), ptr.Val(attachments[1].GetIsInline()))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple attachments without name",
|
||||||
|
transform: func(msg models.Messageable) {
|
||||||
|
attachments := msg.GetAttachments()
|
||||||
|
attachments[1].SetName(ptr.To(""))
|
||||||
|
attachments[2].SetName(ptr.To(""))
|
||||||
|
|
||||||
|
// This test has to be run on a non inline attachment
|
||||||
|
// as inline attachments use contentID instead of name
|
||||||
|
// even when there is a name.
|
||||||
|
assert.False(suite.T(), ptr.Val(attachments[1].GetIsInline()))
|
||||||
|
assert.False(suite.T(), ptr.Val(attachments[2].GetIsInline()))
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, b := range bodies {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
@ -171,7 +215,7 @@ func (suite *EMLUnitSuite) TestConvert_edge_cases() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
body := []byte(testdata.EmailWithAttachments)
|
body := []byte(b)
|
||||||
|
|
||||||
msg, err := api.BytesToMessageable(body)
|
msg, err := api.BytesToMessageable(body)
|
||||||
require.NoError(t, err, "creating message")
|
require.NoError(t, err, "creating message")
|
||||||
@ -193,6 +237,7 @@ func (suite *EMLUnitSuite) TestConvert_edge_cases() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *EMLUnitSuite) TestConvert_eml_ics() {
|
func (suite *EMLUnitSuite) TestConvert_eml_ics() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
@ -228,11 +273,11 @@ func (suite *EMLUnitSuite) TestConvert_eml_ics() {
|
|||||||
|
|
||||||
assert.Equal(
|
assert.Equal(
|
||||||
t,
|
t,
|
||||||
msg.GetCreatedDateTime().Format(ics.ICalDateTimeFormat),
|
msg.GetCreatedDateTime().Format(ics.ICalDateTimeFormatUTC),
|
||||||
event.GetProperty(ical.ComponentPropertyCreated).Value)
|
event.GetProperty(ical.ComponentPropertyCreated).Value)
|
||||||
assert.Equal(
|
assert.Equal(
|
||||||
t,
|
t,
|
||||||
msg.GetLastModifiedDateTime().Format(ics.ICalDateTimeFormat),
|
msg.GetLastModifiedDateTime().Format(ics.ICalDateTimeFormatUTC),
|
||||||
event.GetProperty(ical.ComponentPropertyLastModified).Value)
|
event.GetProperty(ical.ComponentPropertyLastModified).Value)
|
||||||
|
|
||||||
st, err := ics.GetUTCTime(
|
st, err := ics.GetUTCTime(
|
||||||
@ -247,11 +292,11 @@ func (suite *EMLUnitSuite) TestConvert_eml_ics() {
|
|||||||
|
|
||||||
assert.Equal(
|
assert.Equal(
|
||||||
t,
|
t,
|
||||||
st.Format(ics.ICalDateTimeFormat),
|
st.Format(ics.ICalDateTimeFormatUTC),
|
||||||
event.GetProperty(ical.ComponentPropertyDtStart).Value)
|
event.GetProperty(ical.ComponentPropertyDtStart).Value)
|
||||||
assert.Equal(
|
assert.Equal(
|
||||||
t,
|
t,
|
||||||
et.Format(ics.ICalDateTimeFormat),
|
et.Format(ics.ICalDateTimeFormatUTC),
|
||||||
event.GetProperty(ical.ComponentPropertyDtEnd).Value)
|
event.GetProperty(ical.ComponentPropertyDtEnd).Value)
|
||||||
|
|
||||||
tos := msg.GetToRecipients()
|
tos := msg.GetToRecipients()
|
||||||
@ -398,3 +443,48 @@ func (suite *EMLUnitSuite) TestConvert_postable_to_eml() {
|
|||||||
|
|
||||||
assert.Equal(t, source, target)
|
assert.Equal(t, source, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests an ics within an eml within another eml
|
||||||
|
func (suite *EMLUnitSuite) TestConvert_message_in_messageble_to_eml() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
body := []byte(testdata.EmailWithinEmail)
|
||||||
|
|
||||||
|
out, err := FromJSON(ctx, body)
|
||||||
|
assert.NoError(t, err, "converting to eml")
|
||||||
|
|
||||||
|
msg, err := api.BytesToMessageable(body)
|
||||||
|
require.NoError(t, err, "creating message")
|
||||||
|
|
||||||
|
eml, err := enmime.ReadEnvelope(strings.NewReader(out))
|
||||||
|
require.NoError(t, err, "reading created eml")
|
||||||
|
|
||||||
|
assert.Equal(t, ptr.Val(msg.GetSubject()), eml.GetHeader("Subject"))
|
||||||
|
assert.Equal(t, msg.GetSentDateTime().Format(time.RFC1123Z), eml.GetHeader("Date"))
|
||||||
|
|
||||||
|
assert.Equal(t, formatAddress(msg.GetFrom().GetEmailAddress()), eml.GetHeader("From"))
|
||||||
|
|
||||||
|
attachments := eml.Attachments
|
||||||
|
assert.Equal(t, 3, len(attachments), "attachment count in parent email")
|
||||||
|
|
||||||
|
ieml, err := enmime.ReadEnvelope(strings.NewReader(string(attachments[0].Content)))
|
||||||
|
require.NoError(t, err, "reading created eml")
|
||||||
|
|
||||||
|
itm, err := msg.GetAttachments()[0].GetBackingStore().Get("item")
|
||||||
|
require.NoError(t, err, "getting item from message")
|
||||||
|
|
||||||
|
imsg := itm.(*models.Message)
|
||||||
|
assert.Equal(t, ptr.Val(imsg.GetSubject()), ieml.GetHeader("Subject"))
|
||||||
|
assert.Equal(t, imsg.GetSentDateTime().Format(time.RFC1123Z), ieml.GetHeader("Date"))
|
||||||
|
|
||||||
|
assert.Equal(t, formatAddress(imsg.GetFrom().GetEmailAddress()), ieml.GetHeader("From"))
|
||||||
|
|
||||||
|
iattachments := ieml.Attachments
|
||||||
|
assert.Equal(t, 1, len(iattachments), "attachment count in child email")
|
||||||
|
|
||||||
|
// Known from testdata
|
||||||
|
assert.Contains(t, string(iattachments[0].Content), "X-LIC-LOCATION:Africa/Abidjan")
|
||||||
|
}
|
||||||
|
|||||||
@ -104,6 +104,19 @@
|
|||||||
"contentId": null,
|
"contentId": null,
|
||||||
"contentLocation": null,
|
"contentLocation": null,
|
||||||
"contentBytes": "W1BhdGhzXQpQcmVmaXggPSAuLgo="
|
"contentBytes": "W1BhdGhzXQpQcmVmaXggPSAuLgo="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@odata.type": "#microsoft.graph.fileAttachment",
|
||||||
|
"@odata.mediaContentType": "application/octet-stream",
|
||||||
|
"id": "ZZMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAEwbDEWAAABEgAQAD3rU0iyzCdHgz0xmOrWc9g=",
|
||||||
|
"lastModifiedDateTime": "2023-11-16T05:42:47Z",
|
||||||
|
"name": "qt2.conf",
|
||||||
|
"contentType": "application/octet-stream",
|
||||||
|
"size": 156,
|
||||||
|
"isInline": false,
|
||||||
|
"contentId": null,
|
||||||
|
"contentLocation": null,
|
||||||
|
"contentBytes": "Z1BhdGhzXQpQcmVmaXggPSAuLgo="
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
268
src/internal/converters/eml/testdata/email-within-email.json
vendored
Normal file
268
src/internal/converters/eml/testdata/email-within-email.json
vendored
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
{
|
||||||
|
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAA=",
|
||||||
|
"@odata.type": "#microsoft.graph.message",
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages/$entity",
|
||||||
|
"@odata.etag": "W/\"CQAAABYAAABBFDg0JJk7TY1fmsJrh7tNAAFnDeBl\"",
|
||||||
|
"categories": [],
|
||||||
|
"changeKey": "CQAAABYAAABBFDg0JJk7TY1fmsJrh7tNAAFnDeBl",
|
||||||
|
"createdDateTime": "2024-02-05T09:33:23Z",
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:48Z",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAABEgAQAEUyH0VS3HJBgHDlZdWZl0k=",
|
||||||
|
"@odata.type": "#microsoft.graph.itemAttachment",
|
||||||
|
"item@odata.navigationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')",
|
||||||
|
"item@odata.associationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')/$ref",
|
||||||
|
"isInline": false,
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"name": "Purpose of life",
|
||||||
|
"size": 11840,
|
||||||
|
"item": {
|
||||||
|
"id": "",
|
||||||
|
"@odata.type": "#microsoft.graph.message",
|
||||||
|
"createdDateTime": "2024-02-05T09:33:24Z",
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAACEgAQAEUyH0VS3HJBgHDlZdWZl0kSABAAjBhd4-oQaUS969pTkS-gzA==",
|
||||||
|
"@odata.type": "#microsoft.graph.fileAttachment",
|
||||||
|
"@odata.mediaContentType": "text/calendar",
|
||||||
|
"contentType": "text/calendar",
|
||||||
|
"isInline": false,
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"name": "Abidjan.ics",
|
||||||
|
"size": 573,
|
||||||
|
"contentBytes": "QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vdHp1cmwub3JnLy9OT05TR01MIE9sc29uIDIwMjNkLy9FTg0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpBZnJpY2EvQWJpZGphbg0KTEFTVC1NT0RJRklFRDoyMDIzMTIyMlQyMzMzNThaDQpUWlVSTDpodHRwczovL3d3dy50enVybC5vcmcvem9uZWluZm8vQWZyaWNhL0FiaWRqYW4NClgtTElDLUxPQ0FUSU9OOkFmcmljYS9BYmlkamFuDQpYLVBST0xFUFRJQy1UWk5BTUU6TE1UDQpCRUdJTjpTVEFOREFSRA0KVFpOQU1FOkdNVA0KVFpPRkZTRVRGUk9NOi0wMDE2MDgNClRaT0ZGU0VUVE86KzAwMDANCkRUU1RBUlQ6MTkxMjAxMDFUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkVORDpWVElNRVpPTkUNCkVORDpWQ0FMRU5EQVINCg=="
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"content": "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style type=\"text/css\" style=\"display:none;\"> P {margin-top:0;margin-bottom:0;} </style></head><body dir=\"ltr\"><div class=\"elementToProof\" style=\"font-family: Aptos, Aptos_EmbeddedFont, Aptos_MSFontService, Calibri, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);\">I just realized the purpose of my life is to be a test case. Good to know.<br></div></body></html>",
|
||||||
|
"contentType": "html"
|
||||||
|
},
|
||||||
|
"bodyPreview": "I just realized the purpose of my life is to be a test case. Good to know.",
|
||||||
|
"conversationId": "AAQkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNAAQAFEnxDqYmbJEm8d2l3qfS6A=",
|
||||||
|
"conversationIndex": "AQHaWBYiUSfEOpiZskSbx3aXep9LoA==",
|
||||||
|
"flag": {
|
||||||
|
"flagStatus": "notFlagged"
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "JohannaL@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Johanna Lorenz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hasAttachments": true,
|
||||||
|
"importance": "normal",
|
||||||
|
"internetMessageId": "<SJ0PR04MB7294108E381BCCE5C207B6DEBC472@SJ0PR04MB7294.namprd04.prod.outlook.com>",
|
||||||
|
"isDeliveryReceiptRequested": false,
|
||||||
|
"isDraft": false,
|
||||||
|
"isRead": true,
|
||||||
|
"isReadReceiptRequested": false,
|
||||||
|
"receivedDateTime": "2024-02-05T09:33:12Z",
|
||||||
|
"sender": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "JohannaL@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Johanna Lorenz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sentDateTime": "2024-02-05T09:33:11Z",
|
||||||
|
"subject": "Purpose of life",
|
||||||
|
"toRecipients": [
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "PradeepG@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Pradeep Gupta"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"webLink": "https://outlook.office365.com/owa/?AttachmentItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAABEgAQAEUyH0VS3HJBgHDlZdWZl0k%3D&exvsurl=1&viewmodel=ItemAttachment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAABEgAQAEUyH0VS3HJBgHDlZdWZl02=",
|
||||||
|
"@odata.type": "#microsoft.graph.itemAttachment",
|
||||||
|
"item@odata.navigationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')",
|
||||||
|
"item@odata.associationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')/$ref",
|
||||||
|
"isInline": false,
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"name": "Purpose of life part 2",
|
||||||
|
"size": 11840,
|
||||||
|
"item": {
|
||||||
|
"id": "",
|
||||||
|
"@odata.type": "#microsoft.graph.message",
|
||||||
|
"createdDateTime": "2024-02-05T09:33:24Z",
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAACEgAQAEUyH0VS3HJBgHDlZdWZl0kSABAAjBhd4-oQaUS969pTkS-gzA==",
|
||||||
|
"@odata.type": "#microsoft.graph.fileAttachment",
|
||||||
|
"@odata.mediaContentType": "text/calendar",
|
||||||
|
"contentType": "text/calendar",
|
||||||
|
"isInline": false,
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"name": "Abidjan.ics",
|
||||||
|
"size": 573,
|
||||||
|
"contentBytes": "QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vdHp1cmwub3JnLy9OT05TR01MIE9sc29uIDIwMjNkLy9FTg0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpBZnJpY2EvQWJpZGphbg0KTEFTVC1NT0RJRklFRDoyMDIzMTIyMlQyMzMzNThaDQpUWlVSTDpodHRwczovL3d3dy50enVybC5vcmcvem9uZWluZm8vQWZyaWNhL0FiaWRqYW4NClgtTElDLUxPQ0FUSU9OOkFmcmljYS9BYmlkamFuDQpYLVBST0xFUFRJQy1UWk5BTUU6TE1UDQpCRUdJTjpTVEFOREFSRA0KVFpOQU1FOkdNVA0KVFpPRkZTRVRGUk9NOi0wMDE2MDgNClRaT0ZGU0VUVE86KzAwMDANCkRUU1RBUlQ6MTkxMjAxMDFUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkVORDpWVElNRVpPTkUNCkVORDpWQ0FMRU5EQVINCg=="
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"content": "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style type=\"text/css\" style=\"display:none;\"> P {margin-top:0;margin-bottom:0;} </style></head><body dir=\"ltr\"><div class=\"elementToProof\" style=\"font-family: Aptos, Aptos_EmbeddedFont, Aptos_MSFontService, Calibri, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);\">I just realized the purpose of my life is to be a test case. Good to know.<br></div></body></html>",
|
||||||
|
"contentType": "html"
|
||||||
|
},
|
||||||
|
"bodyPreview": "I just realized the purpose of my life is to be a test case. Good to know.",
|
||||||
|
"conversationId": "AAQkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNAAQAFEnxDqYmbJEm8d2l3qfS6A=",
|
||||||
|
"conversationIndex": "AQHaWBYiUSfEOpiZskSbx3aXep9LoA==",
|
||||||
|
"flag": {
|
||||||
|
"flagStatus": "notFlagged"
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "JohannaL@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Johanna Lorenz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hasAttachments": true,
|
||||||
|
"importance": "normal",
|
||||||
|
"internetMessageId": "<SJ0PR04MB7294108E381BCCE5C207B6DEBC472@SJ0PR04MB7294.namprd04.prod.outlook.com>",
|
||||||
|
"isDeliveryReceiptRequested": false,
|
||||||
|
"isDraft": false,
|
||||||
|
"isRead": true,
|
||||||
|
"isReadReceiptRequested": false,
|
||||||
|
"receivedDateTime": "2024-02-05T09:33:12Z",
|
||||||
|
"sender": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "JohannaL@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Johanna Lorenz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sentDateTime": "2024-02-05T09:33:11Z",
|
||||||
|
"subject": "Purpose of life",
|
||||||
|
"toRecipients": [
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "PradeepG@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Pradeep Gupta"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"webLink": "https://outlook.office365.com/owa/?AttachmentItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAABEgAQAEUyH0VS3HJBgHDlZdWZl02%3D&exvsurl=1&viewmodel=ItemAttachment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAABEgAQAEUyH0VS3HJBgHDlZdWZl03=",
|
||||||
|
"@odata.type": "#microsoft.graph.itemAttachment",
|
||||||
|
"item@odata.navigationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')",
|
||||||
|
"item@odata.associationLink": "https://graph.microsoft.com/v1.0/users('7ceb8e03-bdc5-4509-a136-457526165ec0')/messages('')/$ref",
|
||||||
|
"isInline": false,
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"name": "Purpose of life part 3",
|
||||||
|
"size": 11840,
|
||||||
|
"item": {
|
||||||
|
"id": "",
|
||||||
|
"@odata.type": "#microsoft.graph.message",
|
||||||
|
"createdDateTime": "2024-02-05T09:33:24Z",
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"id": "AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV-qAAACEgAQAEUyH0VS3HJBgHDlZdWZl0kSABAAjBhd4-oQaUS969pTkS-gzA==",
|
||||||
|
"@odata.type": "#microsoft.graph.fileAttachment",
|
||||||
|
"@odata.mediaContentType": "text/calendar",
|
||||||
|
"contentType": "text/calendar",
|
||||||
|
"isInline": false,
|
||||||
|
"lastModifiedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"name": "Abidjan.ics",
|
||||||
|
"size": 573,
|
||||||
|
"contentBytes": "QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vdHp1cmwub3JnLy9OT05TR01MIE9sc29uIDIwMjNkLy9FTg0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpBZnJpY2EvQWJpZGphbg0KTEFTVC1NT0RJRklFRDoyMDIzMTIyMlQyMzMzNThaDQpUWlVSTDpodHRwczovL3d3dy50enVybC5vcmcvem9uZWluZm8vQWZyaWNhL0FiaWRqYW4NClgtTElDLUxPQ0FUSU9OOkFmcmljYS9BYmlkamFuDQpYLVBST0xFUFRJQy1UWk5BTUU6TE1UDQpCRUdJTjpTVEFOREFSRA0KVFpOQU1FOkdNVA0KVFpPRkZTRVRGUk9NOi0wMDE2MDgNClRaT0ZGU0VUVE86KzAwMDANCkRUU1RBUlQ6MTkxMjAxMDFUMDAwMDAwDQpFTkQ6U1RBTkRBUkQNCkVORDpWVElNRVpPTkUNCkVORDpWQ0FMRU5EQVINCg=="
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"content": "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style type=\"text/css\" style=\"display:none;\"> P {margin-top:0;margin-bottom:0;} </style></head><body dir=\"ltr\"><div class=\"elementToProof\" style=\"font-family: Aptos, Aptos_EmbeddedFont, Aptos_MSFontService, Calibri, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);\">I just realized the purpose of my life is to be a test case. Good to know.<br></div></body></html>",
|
||||||
|
"contentType": "html"
|
||||||
|
},
|
||||||
|
"bodyPreview": "I just realized the purpose of my life is to be a test case. Good to know.",
|
||||||
|
"conversationId": "AAQkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNAAQAFEnxDqYmbJEm8d2l3qfS6A=",
|
||||||
|
"conversationIndex": "AQHaWBYiUSfEOpiZskSbx3aXep9LoA==",
|
||||||
|
"flag": {
|
||||||
|
"flagStatus": "notFlagged"
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "JohannaL@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Johanna Lorenz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hasAttachments": true,
|
||||||
|
"importance": "normal",
|
||||||
|
"internetMessageId": "<SJ0PR04MB7294108E381BCCE5C207B6DEBC472@SJ0PR04MB7294.namprd04.prod.outlook.com>",
|
||||||
|
"isDeliveryReceiptRequested": false,
|
||||||
|
"isDraft": false,
|
||||||
|
"isRead": true,
|
||||||
|
"isReadReceiptRequested": false,
|
||||||
|
"receivedDateTime": "2024-02-05T09:33:12Z",
|
||||||
|
"sender": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "JohannaL@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Johanna Lorenz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sentDateTime": "2024-02-05T09:33:11Z",
|
||||||
|
"subject": "Purpose of life",
|
||||||
|
"toRecipients": [
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "PradeepG@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Pradeep Gupta"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"webLink": "https://outlook.office365.com/owa/?AttachmentItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAABEgAQAEUyH0VS3HJBgHDlZdWZl03%3D&exvsurl=1&viewmodel=ItemAttachment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bccRecipients": [],
|
||||||
|
"body": {
|
||||||
|
"content": "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style type=\"text/css\" style=\"display:none\">\r\n<!--\r\np\r\n\t{margin-top:0;\r\n\tmargin-bottom:0}\r\n-->\r\n</style></head><body dir=\"ltr\"><div><span class=\"elementToProof\" style=\"font-family:Aptos,Aptos_EmbeddedFont,Aptos_MSFontService,Calibri,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)\">Now, this is what we call nesting in this business.<br></span></div></body></html>",
|
||||||
|
"contentType": "html"
|
||||||
|
},
|
||||||
|
"bodyPreview": "Now, this is what we call nesting in this business.",
|
||||||
|
"ccRecipients": [],
|
||||||
|
"conversationId": "AAQkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNAAQAIv2-4RHwDhJhlqBV5PTE3Y=",
|
||||||
|
"conversationIndex": "AQHaWBZdi/b/hEfAOEmGWoFXk9MTdg==",
|
||||||
|
"flag": {
|
||||||
|
"flagStatus": "notFlagged"
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "JohannaL@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Johanna Lorenz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hasAttachments": true,
|
||||||
|
"importance": "normal",
|
||||||
|
"inferenceClassification": "focused",
|
||||||
|
"internetMessageId": "<SJ0PR04MB729409CE8C191E01151C110DBC472@SJ0PR04MB7294.namprd04.prod.outlook.com>",
|
||||||
|
"isDeliveryReceiptRequested": false,
|
||||||
|
"isDraft": false,
|
||||||
|
"isRead": true,
|
||||||
|
"isReadReceiptRequested": false,
|
||||||
|
"parentFolderId": "AQMkAGJiAGZhNjRlOC00OGI5LTQyNTItYjFkMy00NTJjMTgyZGZkMjQALgAAA0V2IruiJ9ZFvgAO6qBJFycBAEEUODQkmTtNjV_awmuHu00AAAIBCQAAAA==",
|
||||||
|
"receivedDateTime": "2024-02-05T09:33:46Z",
|
||||||
|
"replyTo": [],
|
||||||
|
"sender": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "JohannaL@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Johanna Lorenz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sentDateTime": "2024-02-05T09:33:45Z",
|
||||||
|
"subject": "Fw: Purpose of life",
|
||||||
|
"toRecipients": [
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "PradeepG@10rqc2.onmicrosoft.com",
|
||||||
|
"name": "Pradeep Gupta"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"webLink": "https://outlook.office365.com/owa/?ItemID=AAMkAGJiZmE2NGU4LTQ4YjktNDI1Mi1iMWQzLTQ1MmMxODJkZmQyNABGAAAAAABFdiK7oifWRb4ADuqgSRcnBwBBFDg0JJk7TY1fmsJrh7tNAAAAAAEJAABBFDg0JJk7TY1fmsJrh7tNAAFnbV%2FqAAA%3D&exvsurl=1&viewmodel=ReadMessageItem"
|
||||||
|
}
|
||||||
@ -10,3 +10,6 @@ var EmailWithEventInfo string
|
|||||||
|
|
||||||
//go:embed email-with-event-object.json
|
//go:embed email-with-event-object.json
|
||||||
var EmailWithEventObject string
|
var EmailWithEventObject string
|
||||||
|
|
||||||
|
//go:embed email-within-email.json
|
||||||
|
var EmailWithinEmail string
|
||||||
|
|||||||
@ -166,3 +166,20 @@ var GraphTimeZoneToTZ = map[string]string{
|
|||||||
"Yukon Standard Time": "America/Whitehorse",
|
"Yukon Standard Time": "America/Whitehorse",
|
||||||
"tzone://Microsoft/Utc": "Etc/UTC",
|
"tzone://Microsoft/Utc": "Etc/UTC",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map from alternatives to the canonical time zone name
|
||||||
|
// There mapping are currently generated by manually going on the
|
||||||
|
// values in the GraphTimeZoneToTZ which is not available in the tzdb
|
||||||
|
var CanonicalTimeZoneMap = map[string]string{
|
||||||
|
"Africa/Asmara": "Africa/Asmera",
|
||||||
|
"Asia/Calcutta": "Asia/Kolkata",
|
||||||
|
"Asia/Rangoon": "Asia/Yangon",
|
||||||
|
"Asia/Saigon": "Asia/Ho_Chi_Minh",
|
||||||
|
"Europe/Kiev": "Europe/Kyiv",
|
||||||
|
"Europe/Warsaw": "Europe/Warszawa",
|
||||||
|
"America/Buenos_Aires": "America/Argentina/Buenos_Aires",
|
||||||
|
"America/Godthab": "America/Nuuk",
|
||||||
|
// NOTE: "Atlantic/Raykjavik" missing in tzdb but is in MS list
|
||||||
|
|
||||||
|
"Etc/UTC": "UTC", // simplifying the time zone name
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
@ -16,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/common/str"
|
"github.com/alcionai/corso/src/internal/common/str"
|
||||||
|
"github.com/alcionai/corso/src/internal/converters/ics/tzdata"
|
||||||
"github.com/alcionai/corso/src/pkg/dttm"
|
"github.com/alcionai/corso/src/pkg/dttm"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -31,7 +33,8 @@ import (
|
|||||||
// TODO locations: https://github.com/alcionai/corso/issues/5003
|
// TODO locations: https://github.com/alcionai/corso/issues/5003
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ICalDateTimeFormat = "20060102T150405Z"
|
ICalDateTimeFormat = "20060102T150405"
|
||||||
|
ICalDateTimeFormatUTC = "20060102T150405Z"
|
||||||
ICalDateFormat = "20060102"
|
ICalDateFormat = "20060102"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -172,6 +175,17 @@ func getRecurrencePattern(
|
|||||||
recurComponents = append(recurComponents, "BYDAY="+prefix+strings.Join(dowComponents, ","))
|
recurComponents = append(recurComponents, "BYDAY="+prefix+strings.Join(dowComponents, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is necessary to compute when weekly events recur
|
||||||
|
fdow := pat.GetFirstDayOfWeek()
|
||||||
|
if fdow != nil {
|
||||||
|
icalday, ok := GraphToICalDOW[fdow.String()]
|
||||||
|
if !ok {
|
||||||
|
return "", clues.NewWC(ctx, "unknown first day of week").With("day", fdow)
|
||||||
|
}
|
||||||
|
|
||||||
|
recurComponents = append(recurComponents, "WKST="+icalday)
|
||||||
|
}
|
||||||
|
|
||||||
rrange := recurrence.GetRangeEscaped()
|
rrange := recurrence.GetRangeEscaped()
|
||||||
if rrange != nil {
|
if rrange != nil {
|
||||||
switch ptr.Val(rrange.GetTypeEscaped()) {
|
switch ptr.Val(rrange.GetTypeEscaped()) {
|
||||||
@ -195,7 +209,7 @@ func getRecurrencePattern(
|
|||||||
return "", clues.WrapWC(ctx, err, "parsing end time")
|
return "", clues.WrapWC(ctx, err, "parsing end time")
|
||||||
}
|
}
|
||||||
|
|
||||||
recurComponents = append(recurComponents, "UNTIL="+endTime.Format(ICalDateTimeFormat))
|
recurComponents = append(recurComponents, "UNTIL="+endTime.Format(ICalDateTimeFormatUTC))
|
||||||
}
|
}
|
||||||
case models.NOEND_RECURRENCERANGETYPE:
|
case models.NOEND_RECURRENCERANGETYPE:
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
@ -224,10 +238,15 @@ func FromEventable(ctx context.Context, event models.Eventable) (string, error)
|
|||||||
cal := ics.NewCalendar()
|
cal := ics.NewCalendar()
|
||||||
cal.SetProductId("-//Alcion//Corso") // Does this have to be customizable?
|
cal.SetProductId("-//Alcion//Corso") // Does this have to be customizable?
|
||||||
|
|
||||||
|
err := addTimeZoneComponents(ctx, cal, event)
|
||||||
|
if err != nil {
|
||||||
|
return "", clues.Wrap(err, "adding timezone components")
|
||||||
|
}
|
||||||
|
|
||||||
id := ptr.Val(event.GetId())
|
id := ptr.Val(event.GetId())
|
||||||
iCalEvent := cal.AddEvent(id)
|
iCalEvent := cal.AddEvent(id)
|
||||||
|
|
||||||
err := updateEventProperties(ctx, event, iCalEvent)
|
err = updateEventProperties(ctx, event, iCalEvent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", clues.Wrap(err, "updating event properties")
|
return "", clues.Wrap(err, "updating event properties")
|
||||||
}
|
}
|
||||||
@ -258,7 +277,7 @@ func FromEventable(ctx context.Context, event models.Eventable) (string, error)
|
|||||||
exICalEvent := cal.AddEvent(id)
|
exICalEvent := cal.AddEvent(id)
|
||||||
start := exception.GetOriginalStart() // will always be in UTC
|
start := exception.GetOriginalStart() // will always be in UTC
|
||||||
|
|
||||||
exICalEvent.AddProperty(ics.ComponentProperty(ics.PropertyRecurrenceId), start.Format(ICalDateTimeFormat))
|
exICalEvent.AddProperty(ics.ComponentProperty(ics.PropertyRecurrenceId), start.Format(ICalDateTimeFormatUTC))
|
||||||
|
|
||||||
err = updateEventProperties(ctx, exception, exICalEvent)
|
err = updateEventProperties(ctx, exception, exICalEvent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -269,6 +288,91 @@ func FromEventable(ctx context.Context, event models.Eventable) (string, error)
|
|||||||
return cal.Serialize(), nil
|
return cal.Serialize(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTZDataKeyValues(ctx context.Context, timezone string) (map[string]string, error) {
|
||||||
|
template, ok := tzdata.TZData[timezone]
|
||||||
|
if !ok {
|
||||||
|
return nil, clues.NewWC(ctx, "timezone not found in tz database").
|
||||||
|
With("timezone", timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyValues := map[string]string{}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(template, "\n") {
|
||||||
|
splits := strings.SplitN(line, ":", 2)
|
||||||
|
if len(splits) != 2 {
|
||||||
|
return nil, clues.NewWC(ctx, "invalid tzdata line").
|
||||||
|
With("line", line).
|
||||||
|
With("timezone", timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyValues[splits[0]] = splits[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyValues, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTimeZoneComponents(ctx context.Context, cal *ics.Calendar, event models.Eventable) error {
|
||||||
|
// Handling of timezone get a bit tricky when we have to deal with
|
||||||
|
// relative recurrence. The issue comes up when we set a recurrence
|
||||||
|
// to be something like "repeat every 3rd Tuesday". Tuesday in UTC
|
||||||
|
// and in IST will be different and so we cannot just always use UTC.
|
||||||
|
//
|
||||||
|
// The way this is solved is by using the timezone in the
|
||||||
|
// recurrence for start and end timezones as we have to use UTC
|
||||||
|
// for UNTIL(mostly).
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc5545#section-3.3.10
|
||||||
|
timezone, err := getRecurrenceTimezone(ctx, event)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Stack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timezone != time.UTC {
|
||||||
|
kvs, err := getTZDataKeyValues(ctx, timezone.String())
|
||||||
|
if err != nil {
|
||||||
|
return clues.Stack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tz := cal.AddTimezone(timezone.String())
|
||||||
|
|
||||||
|
for k, v := range kvs {
|
||||||
|
tz.AddProperty(ics.ComponentProperty(k), v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRecurrenceTimezone get the timezone specified by the recurrence
|
||||||
|
// in the calendar. It does a normalization pass where we always convert
|
||||||
|
// the timezone to the value in tzdb If we don't have a recurrence
|
||||||
|
// timezone, we don't have to use a specific timezone in the export and
|
||||||
|
// is safe to return UTC from this method.
|
||||||
|
func getRecurrenceTimezone(ctx context.Context, event models.Eventable) (*time.Location, error) {
|
||||||
|
if event.GetRecurrence() != nil {
|
||||||
|
timezone := ptr.Val(event.GetRecurrence().GetRangeEscaped().GetRecurrenceTimeZone())
|
||||||
|
|
||||||
|
ctz, ok := GraphTimeZoneToTZ[timezone]
|
||||||
|
if ok {
|
||||||
|
timezone = ctz
|
||||||
|
}
|
||||||
|
|
||||||
|
cannon, ok := CanonicalTimeZoneMap[timezone]
|
||||||
|
if ok {
|
||||||
|
timezone = cannon
|
||||||
|
}
|
||||||
|
|
||||||
|
loc, err := time.LoadLocation(timezone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.WrapWC(ctx, err, "unknown timezone").
|
||||||
|
With("timezone", timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
return loc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.UTC, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isASCII(s string) bool {
|
func isASCII(s string) bool {
|
||||||
for _, c := range s {
|
for _, c := range s {
|
||||||
if c > unicode.MaxASCII {
|
if c > unicode.MaxASCII {
|
||||||
@ -279,6 +383,12 @@ func isASCII(s string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if a given string is a valid email address
|
||||||
|
func isEmail(em string) bool {
|
||||||
|
_, err := mail.ParseAddress(em)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
func updateEventProperties(ctx context.Context, event models.Eventable, iCalEvent *ics.VEvent) error {
|
func updateEventProperties(ctx context.Context, event models.Eventable, iCalEvent *ics.VEvent) error {
|
||||||
// CREATED - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.7.1
|
// CREATED - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.7.1
|
||||||
created := event.GetCreatedDateTime()
|
created := event.GetCreatedDateTime()
|
||||||
@ -292,6 +402,11 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
|
|||||||
iCalEvent.SetModifiedAt(ptr.Val(modified))
|
iCalEvent.SetModifiedAt(ptr.Val(modified))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timezone, err := getRecurrenceTimezone(ctx, event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// DTSTART - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.4
|
// DTSTART - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.4
|
||||||
allDay := ptr.Val(event.GetIsAllDay())
|
allDay := ptr.Val(event.GetIsAllDay())
|
||||||
startString := event.GetStart().GetDateTime()
|
startString := event.GetStart().GetDateTime()
|
||||||
@ -303,11 +418,7 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
|
|||||||
return clues.WrapWC(ctx, err, "parsing start time")
|
return clues.WrapWC(ctx, err, "parsing start time")
|
||||||
}
|
}
|
||||||
|
|
||||||
if allDay {
|
addTime(iCalEvent, ics.ComponentPropertyDtStart, start, allDay, timezone)
|
||||||
iCalEvent.SetStartAt(start, ics.WithValue(string(ics.ValueDataTypeDate)))
|
|
||||||
} else {
|
|
||||||
iCalEvent.SetStartAt(start)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DTEND - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.2
|
// DTEND - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.2
|
||||||
@ -320,11 +431,7 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
|
|||||||
return clues.WrapWC(ctx, err, "parsing end time")
|
return clues.WrapWC(ctx, err, "parsing end time")
|
||||||
}
|
}
|
||||||
|
|
||||||
if allDay {
|
addTime(iCalEvent, ics.ComponentPropertyDtEnd, end, allDay, timezone)
|
||||||
iCalEvent.SetEndAt(end, ics.WithValue(string(ics.ValueDataTypeDate)))
|
|
||||||
} else {
|
|
||||||
iCalEvent.SetEndAt(end)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recurrence := event.GetRecurrence()
|
recurrence := event.GetRecurrence()
|
||||||
@ -377,7 +484,14 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
|
|||||||
desc := replacer.Replace(description)
|
desc := replacer.Replace(description)
|
||||||
iCalEvent.AddProperty("X-ALT-DESC", desc, ics.WithFmtType("text/html"))
|
iCalEvent.AddProperty("X-ALT-DESC", desc, ics.WithFmtType("text/html"))
|
||||||
} else {
|
} else {
|
||||||
stripped, err := html2text.FromString(description, html2text.Options{PrettyTables: true})
|
// Disable auto wrap, causes huge memory spikes
|
||||||
|
// https://github.com/jaytaylor/html2text/issues/48
|
||||||
|
prettyTablesOptions := html2text.NewPrettyTablesOptions()
|
||||||
|
prettyTablesOptions.AutoWrapText = false
|
||||||
|
|
||||||
|
stripped, err := html2text.FromString(
|
||||||
|
description,
|
||||||
|
html2text.Options{PrettyTables: true, PrettyTablesOptions: prettyTablesOptions})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "converting html to text").
|
return clues.Wrap(err, "converting html to text").
|
||||||
With("description_length", len(description))
|
With("description_length", len(description))
|
||||||
@ -481,8 +595,21 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It is possible that we get non email items like the below
|
||||||
|
// one which is an internal representation of the user in the
|
||||||
|
// Exchange system. While we can technically output this as an
|
||||||
|
// attendee, it is not useful plus other downstream tools like
|
||||||
|
// ones to use PST can choke on this.
|
||||||
|
// /o=ExchangeLabs/ou=ExchangeAdministrative Group(FY...LT)/cn=Recipients/cn=883...4a-John Doe
|
||||||
addr := ptr.Val(attendee.GetEmailAddress().GetAddress())
|
addr := ptr.Val(attendee.GetEmailAddress().GetAddress())
|
||||||
|
if isEmail(addr) {
|
||||||
iCalEvent.AddAttendee(addr, props...)
|
iCalEvent.AddAttendee(addr, props...)
|
||||||
|
} else {
|
||||||
|
logger.Ctx(ctx).
|
||||||
|
With("attendee_email", addr).
|
||||||
|
With("attendee_name", name).
|
||||||
|
Info("skipping non email attendee from ics export")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOCATION - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.7
|
// LOCATION - https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.7
|
||||||
@ -610,6 +737,26 @@ func updateEventProperties(ctx context.Context, event models.Eventable, iCalEven
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addTime(iCalEvent *ics.VEvent, prop ics.ComponentProperty, tm time.Time, allDay bool, tzLoc *time.Location) {
|
||||||
|
if allDay {
|
||||||
|
if tzLoc == time.UTC {
|
||||||
|
iCalEvent.SetProperty(prop, tm.Format(ICalDateFormat), ics.WithValue(string(ics.ValueDataTypeDate)))
|
||||||
|
} else {
|
||||||
|
iCalEvent.SetProperty(
|
||||||
|
prop,
|
||||||
|
tm.In(tzLoc).Format(ICalDateFormat),
|
||||||
|
ics.WithValue(string(ics.ValueDataTypeDate)),
|
||||||
|
keyValues("TZID", tzLoc.String()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tzLoc == time.UTC {
|
||||||
|
iCalEvent.SetProperty(prop, tm.Format(ICalDateTimeFormatUTC))
|
||||||
|
} else {
|
||||||
|
iCalEvent.SetProperty(prop, tm.In(tzLoc).Format(ICalDateTimeFormat), keyValues("TZID", tzLoc.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getCancelledDates(ctx context.Context, event models.Eventable) ([]time.Time, error) {
|
func getCancelledDates(ctx context.Context, event models.Eventable) ([]time.Time, error) {
|
||||||
dateStrings, err := api.GetCancelledEventDateStrings(event)
|
dateStrings, err := api.GetCancelledEventDateStrings(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ics "github.com/arran4/golang-ical"
|
||||||
"github.com/microsoft/kiota-abstractions-go/serialization"
|
"github.com/microsoft/kiota-abstractions-go/serialization"
|
||||||
kjson "github.com/microsoft/kiota-serialization-json-go"
|
kjson "github.com/microsoft/kiota-serialization-json-go"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -21,6 +22,7 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
|
"github.com/alcionai/corso/src/internal/converters/ics/tzdata"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ func TestICSUnitSuite(t *testing.T) {
|
|||||||
suite.Run(t, &ICSUnitSuite{Suite: tester.NewUnitSuite(t)})
|
suite.Run(t, &ICSUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ICSUnitSuite) TestGetLocationString() {
|
func (s *ICSUnitSuite) TestGetLocationString() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
loc func() models.Locationable
|
loc func() models.Locationable
|
||||||
@ -110,13 +112,13 @@ func (suite *ICSUnitSuite) TestGetLocationString() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
suite.Run(tt.name, func() {
|
s.Run(tt.name, func() {
|
||||||
assert.Equal(suite.T(), tt.expect, getLocationString(tt.loc()))
|
assert.Equal(s.T(), tt.expect, getLocationString(tt.loc()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ICSUnitSuite) TestGetUTCTime() {
|
func (s *ICSUnitSuite) TestGetUTCTime() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
timestamp string
|
timestamp string
|
||||||
@ -162,18 +164,18 @@ func (suite *ICSUnitSuite) TestGetUTCTime() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
suite.Run(tt.name, func() {
|
s.Run(tt.name, func() {
|
||||||
t, err := GetUTCTime(tt.timestamp, tt.timezone)
|
t, err := GetUTCTime(tt.timestamp, tt.timezone)
|
||||||
tt.errCheck(suite.T(), err)
|
tt.errCheck(s.T(), err)
|
||||||
|
|
||||||
if !tt.time.Equal(time.Time{}) {
|
if !tt.time.Equal(time.Time{}) {
|
||||||
assert.Equal(suite.T(), tt.time, t)
|
assert.Equal(s.T(), tt.time, t)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
func (s *ICSUnitSuite) TestGetRecurrencePattern() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
recurrence func() models.PatternedRecurrenceable
|
recurrence func() models.PatternedRecurrenceable
|
||||||
@ -187,16 +189,37 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("daily")
|
typ, err := models.ParseRecurrencePatternType("daily")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
rec.SetPattern(pat)
|
rec.SetPattern(pat)
|
||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=DAILY;INTERVAL=1",
|
expect: "FREQ=DAILY;INTERVAL=1;WKST=SU",
|
||||||
|
errCheck: require.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "daily different start of week",
|
||||||
|
recurrence: func() models.PatternedRecurrenceable {
|
||||||
|
rec := models.NewPatternedRecurrence()
|
||||||
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
|
typ, err := models.ParseRecurrencePatternType("daily")
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.MONDAY_DAYOFWEEK))
|
||||||
|
|
||||||
|
rec.SetPattern(pat)
|
||||||
|
|
||||||
|
return rec
|
||||||
|
},
|
||||||
|
expect: "FREQ=DAILY;INTERVAL=1;WKST=MO",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -206,15 +229,16 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("daily")
|
typ, err := models.ParseRecurrencePatternType("daily")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
rng := models.NewRecurrenceRange()
|
rng := models.NewRecurrenceRange()
|
||||||
|
|
||||||
rrtype, err := models.ParseRecurrenceRangeType("endDate")
|
rrtype, err := models.ParseRecurrenceRangeType("endDate")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
|
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
|
||||||
|
|
||||||
@ -227,7 +251,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=DAILY;INTERVAL=1;UNTIL=20210101T182959Z",
|
expect: "FREQ=DAILY;INTERVAL=1;WKST=SU;UNTIL=20210101T182959Z",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -237,16 +261,17 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("weekly")
|
typ, err := models.ParseRecurrencePatternType("weekly")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
rec.SetPattern(pat)
|
rec.SetPattern(pat)
|
||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=WEEKLY;INTERVAL=1",
|
expect: "FREQ=WEEKLY;INTERVAL=1;WKST=SU",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -256,15 +281,16 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("weekly")
|
typ, err := models.ParseRecurrencePatternType("weekly")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
rng := models.NewRecurrenceRange()
|
rng := models.NewRecurrenceRange()
|
||||||
|
|
||||||
rrtype, err := models.ParseRecurrenceRangeType("endDate")
|
rrtype, err := models.ParseRecurrenceRangeType("endDate")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
|
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
|
||||||
|
|
||||||
@ -277,7 +303,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=WEEKLY;INTERVAL=1;UNTIL=20210101T235959Z",
|
expect: "FREQ=WEEKLY;INTERVAL=1;WKST=SU;UNTIL=20210101T235959Z",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -287,15 +313,16 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("weekly")
|
typ, err := models.ParseRecurrencePatternType("weekly")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
rng := models.NewRecurrenceRange()
|
rng := models.NewRecurrenceRange()
|
||||||
|
|
||||||
rrtype, err := models.ParseRecurrenceRangeType("numbered")
|
rrtype, err := models.ParseRecurrenceRangeType("numbered")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
|
rng.SetTypeEscaped(rrtype.(*models.RecurrenceRangeType))
|
||||||
|
|
||||||
@ -307,7 +334,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=WEEKLY;INTERVAL=1;COUNT=10",
|
expect: "FREQ=WEEKLY;INTERVAL=1;WKST=SU;COUNT=10",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -317,10 +344,11 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("weekly")
|
typ, err := models.ParseRecurrencePatternType("weekly")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
days := []models.DayOfWeek{
|
days := []models.DayOfWeek{
|
||||||
models.MONDAY_DAYOFWEEK,
|
models.MONDAY_DAYOFWEEK,
|
||||||
@ -334,7 +362,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,TH",
|
expect: "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,TH;WKST=SU",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -344,16 +372,17 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("daily")
|
typ, err := models.ParseRecurrencePatternType("daily")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(2)))
|
pat.SetInterval(ptr.To(int32(2)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
rec.SetPattern(pat)
|
rec.SetPattern(pat)
|
||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=DAILY;INTERVAL=2",
|
expect: "FREQ=DAILY;INTERVAL=2;WKST=SU",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -363,10 +392,11 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("absoluteMonthly")
|
typ, err := models.ParseRecurrencePatternType("absoluteMonthly")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
pat.SetDayOfMonth(ptr.To(int32(5)))
|
pat.SetDayOfMonth(ptr.To(int32(5)))
|
||||||
|
|
||||||
@ -374,7 +404,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=5",
|
expect: "FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=5;WKST=SU",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -384,10 +414,11 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("absoluteYearly")
|
typ, err := models.ParseRecurrencePatternType("absoluteYearly")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(3)))
|
pat.SetInterval(ptr.To(int32(3)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
pat.SetMonth(ptr.To(int32(8)))
|
pat.SetMonth(ptr.To(int32(8)))
|
||||||
|
|
||||||
@ -395,7 +426,7 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=YEARLY;INTERVAL=3;BYMONTH=8",
|
expect: "FREQ=YEARLY;INTERVAL=3;BYMONTH=8;WKST=SU",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -405,37 +436,38 @@ func (suite *ICSUnitSuite) TestGetRecurrencePattern() {
|
|||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("relativeYearly")
|
typ, err := models.ParseRecurrencePatternType("relativeYearly")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
pat.SetMonth(ptr.To(int32(8)))
|
pat.SetMonth(ptr.To(int32(8)))
|
||||||
pat.SetDaysOfWeek([]models.DayOfWeek{models.FRIDAY_DAYOFWEEK})
|
pat.SetDaysOfWeek([]models.DayOfWeek{models.FRIDAY_DAYOFWEEK})
|
||||||
|
|
||||||
wi, err := models.ParseWeekIndex("first")
|
wi, err := models.ParseWeekIndex("first")
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(s.T(), err)
|
||||||
pat.SetIndex(wi.(*models.WeekIndex))
|
pat.SetIndex(wi.(*models.WeekIndex))
|
||||||
|
|
||||||
rec.SetPattern(pat)
|
rec.SetPattern(pat)
|
||||||
|
|
||||||
return rec
|
return rec
|
||||||
},
|
},
|
||||||
expect: "FREQ=YEARLY;INTERVAL=1;BYMONTH=8;BYDAY=1FR",
|
expect: "FREQ=YEARLY;INTERVAL=1;BYMONTH=8;BYDAY=1FR;WKST=SU",
|
||||||
errCheck: require.NoError,
|
errCheck: require.NoError,
|
||||||
},
|
},
|
||||||
// TODO(meain): could still use more tests for edge cases of time
|
// TODO(meain): could still use more tests for edge cases of time
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
suite.Run(tt.name, func() {
|
s.Run(tt.name, func() {
|
||||||
ctx, flush := tester.NewContext(suite.T())
|
ctx, flush := tester.NewContext(s.T())
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
rec, err := getRecurrencePattern(ctx, tt.recurrence())
|
rec, err := getRecurrencePattern(ctx, tt.recurrence())
|
||||||
tt.errCheck(suite.T(), err)
|
tt.errCheck(s.T(), err)
|
||||||
|
|
||||||
assert.Equal(suite.T(), tt.expect, rec)
|
assert.Equal(s.T(), tt.expect, rec)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -460,8 +492,8 @@ func baseEvent() *models.Event {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ICSUnitSuite) TestEventConversion() {
|
func (s *ICSUnitSuite) TestEventConversion() {
|
||||||
t := suite.T()
|
t := s.T()
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
@ -546,14 +578,19 @@ func (suite *ICSUnitSuite) TestEventConversion() {
|
|||||||
|
|
||||||
rec := models.NewPatternedRecurrence()
|
rec := models.NewPatternedRecurrence()
|
||||||
pat := models.NewRecurrencePattern()
|
pat := models.NewRecurrencePattern()
|
||||||
|
rng := models.NewRecurrenceRange()
|
||||||
|
|
||||||
typ, err := models.ParseRecurrencePatternType("daily")
|
typ, err := models.ParseRecurrencePatternType("daily")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
pat.SetTypeEscaped(typ.(*models.RecurrencePatternType))
|
||||||
pat.SetInterval(ptr.To(int32(1)))
|
pat.SetInterval(ptr.To(int32(1)))
|
||||||
|
pat.SetFirstDayOfWeek(ptr.To(models.SUNDAY_DAYOFWEEK))
|
||||||
|
|
||||||
|
rng.SetRecurrenceTimeZone(ptr.To("UTC"))
|
||||||
|
|
||||||
rec.SetPattern(pat)
|
rec.SetPattern(pat)
|
||||||
|
rec.SetRangeEscaped(rng)
|
||||||
|
|
||||||
e.SetRecurrence(rec)
|
e.SetRecurrence(rec)
|
||||||
|
|
||||||
@ -830,8 +867,8 @@ func (suite *ICSUnitSuite) TestEventConversion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
suite.Run(tt.name, func() {
|
s.Run(tt.name, func() {
|
||||||
t := suite.T()
|
t := s.T()
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
@ -881,8 +918,8 @@ func checkAttendee(t *testing.T, out, check, msg string) {
|
|||||||
assert.ElementsMatch(t, as, bs, fmt.Sprintf("fields %s", msg))
|
assert.ElementsMatch(t, as, bs, fmt.Sprintf("fields %s", msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ICSUnitSuite) TestAttendees() {
|
func (s *ICSUnitSuite) TestAttendees() {
|
||||||
t := suite.T()
|
t := s.T()
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
@ -908,6 +945,17 @@ func (suite *ICSUnitSuite) TestAttendees() {
|
|||||||
"attendee")
|
"attendee")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "attendee with internal exchange representation for email",
|
||||||
|
att: [][]string{{
|
||||||
|
"/o=ExchangeLabs/ou=ExchangeAdministrative Group(FY...LT)/cn=Recipients/cn=883...4a-John Doe",
|
||||||
|
"required",
|
||||||
|
"declined",
|
||||||
|
}},
|
||||||
|
check: func(out string) {
|
||||||
|
assert.NotContains(t, out, "ATTENDEE")
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "multiple attendees",
|
name: "multiple attendees",
|
||||||
att: [][]string{
|
att: [][]string{
|
||||||
@ -938,8 +986,8 @@ func (suite *ICSUnitSuite) TestAttendees() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
suite.Run(tt.name, func() {
|
s.Run(tt.name, func() {
|
||||||
t := suite.T()
|
t := s.T()
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
@ -1060,8 +1108,8 @@ func checkAttachment(t *testing.T, out, check, msg string) {
|
|||||||
assert.ElementsMatch(t, as, bs, fmt.Sprintf("fields %s", msg))
|
assert.ElementsMatch(t, as, bs, fmt.Sprintf("fields %s", msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ICSUnitSuite) TestAttachments() {
|
func (s *ICSUnitSuite) TestAttachments() {
|
||||||
t := suite.T()
|
t := s.T()
|
||||||
|
|
||||||
type attachment struct {
|
type attachment struct {
|
||||||
cid string // contentid
|
cid string // contentid
|
||||||
@ -1117,8 +1165,8 @@ func (suite *ICSUnitSuite) TestAttachments() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
suite.Run(tt.name, func() {
|
s.Run(tt.name, func() {
|
||||||
t := suite.T()
|
t := s.T()
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
@ -1161,7 +1209,7 @@ func (suite *ICSUnitSuite) TestAttachments() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ICSUnitSuite) TestCancellations() {
|
func (s *ICSUnitSuite) TestCancellations() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
cancelledIds []string
|
cancelledIds []string
|
||||||
@ -1185,8 +1233,8 @@ func (suite *ICSUnitSuite) TestCancellations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
suite.Run(tt.name, func() {
|
s.Run(tt.name, func() {
|
||||||
t := suite.T()
|
t := s.T()
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
@ -1249,7 +1297,7 @@ func eventToJSON(e *models.Event) ([]byte, error) {
|
|||||||
return bts, err
|
return bts, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ICSUnitSuite) TestEventExceptions() {
|
func (s *ICSUnitSuite) TestEventExceptions() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
event func() *models.Event
|
event func() *models.Event
|
||||||
@ -1271,7 +1319,7 @@ func (suite *ICSUnitSuite) TestEventExceptions() {
|
|||||||
exception.SetEnd(newEnd)
|
exception.SetEnd(newEnd)
|
||||||
|
|
||||||
parsed, err := eventToMap(exception)
|
parsed, err := eventToMap(exception)
|
||||||
require.NoError(suite.T(), err, "parsing exception")
|
require.NoError(s.T(), err, "parsing exception")
|
||||||
|
|
||||||
// add exception event to additional data
|
// add exception event to additional data
|
||||||
e.SetAdditionalData(map[string]any{
|
e.SetAdditionalData(map[string]any{
|
||||||
@ -1290,15 +1338,15 @@ func (suite *ICSUnitSuite) TestEventExceptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(suite.T(), 2, events, "number of events")
|
assert.Equal(s.T(), 2, events, "number of events")
|
||||||
|
|
||||||
assert.Contains(suite.T(), out, "RECURRENCE-ID:20210101T120000Z", "recurrence id")
|
assert.Contains(s.T(), out, "RECURRENCE-ID:20210101T120000Z", "recurrence id")
|
||||||
|
|
||||||
assert.Contains(suite.T(), out, "SUMMARY:Subject", "original event")
|
assert.Contains(s.T(), out, "SUMMARY:Subject", "original event")
|
||||||
assert.Contains(suite.T(), out, "SUMMARY:Exception", "exception event")
|
assert.Contains(s.T(), out, "SUMMARY:Exception", "exception event")
|
||||||
|
|
||||||
assert.Contains(suite.T(), out, "DTSTART:20210101T130000Z", "new start time")
|
assert.Contains(s.T(), out, "DTSTART:20210101T130000Z", "new start time")
|
||||||
assert.Contains(suite.T(), out, "DTEND:20210101T140000Z", "new end time")
|
assert.Contains(s.T(), out, "DTEND:20210101T140000Z", "new end time")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1327,10 +1375,10 @@ func (suite *ICSUnitSuite) TestEventExceptions() {
|
|||||||
exception2.SetEnd(newEnd)
|
exception2.SetEnd(newEnd)
|
||||||
|
|
||||||
parsed1, err := eventToMap(exception1)
|
parsed1, err := eventToMap(exception1)
|
||||||
require.NoError(suite.T(), err, "parsing exception 1")
|
require.NoError(s.T(), err, "parsing exception 1")
|
||||||
|
|
||||||
parsed2, err := eventToMap(exception2)
|
parsed2, err := eventToMap(exception2)
|
||||||
require.NoError(suite.T(), err, "parsing exception 2")
|
require.NoError(s.T(), err, "parsing exception 2")
|
||||||
|
|
||||||
// add exception event to additional data
|
// add exception event to additional data
|
||||||
e.SetAdditionalData(map[string]any{
|
e.SetAdditionalData(map[string]any{
|
||||||
@ -1349,36 +1397,230 @@ func (suite *ICSUnitSuite) TestEventExceptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(suite.T(), 3, events, "number of events")
|
assert.Equal(s.T(), 3, events, "number of events")
|
||||||
|
|
||||||
assert.Contains(suite.T(), out, "RECURRENCE-ID:20210101T120000Z", "recurrence id 1")
|
assert.Contains(s.T(), out, "RECURRENCE-ID:20210101T120000Z", "recurrence id 1")
|
||||||
assert.Contains(suite.T(), out, "RECURRENCE-ID:20210102T120000Z", "recurrence id 2")
|
assert.Contains(s.T(), out, "RECURRENCE-ID:20210102T120000Z", "recurrence id 2")
|
||||||
|
|
||||||
assert.Contains(suite.T(), out, "SUMMARY:Subject", "original event")
|
assert.Contains(s.T(), out, "SUMMARY:Subject", "original event")
|
||||||
assert.Contains(suite.T(), out, "SUMMARY:Exception 1", "exception event 1")
|
assert.Contains(s.T(), out, "SUMMARY:Exception 1", "exception event 1")
|
||||||
assert.Contains(suite.T(), out, "SUMMARY:Exception 2", "exception event 2")
|
assert.Contains(s.T(), out, "SUMMARY:Exception 2", "exception event 2")
|
||||||
|
|
||||||
assert.Contains(suite.T(), out, "DTSTART:20210101T130000Z", "new start time 1")
|
assert.Contains(s.T(), out, "DTSTART:20210101T130000Z", "new start time 1")
|
||||||
assert.Contains(suite.T(), out, "DTEND:20210101T140000Z", "new end time 1")
|
assert.Contains(s.T(), out, "DTEND:20210101T140000Z", "new end time 1")
|
||||||
|
|
||||||
assert.Contains(suite.T(), out, "DTSTART:20210102T130000Z", "new start time 2")
|
assert.Contains(s.T(), out, "DTSTART:20210102T130000Z", "new start time 2")
|
||||||
assert.Contains(suite.T(), out, "DTEND:20210102T140000Z", "new end time 2")
|
assert.Contains(s.T(), out, "DTEND:20210102T140000Z", "new end time 2")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range table {
|
for _, tt := range table {
|
||||||
suite.Run(tt.name, func() {
|
s.Run(tt.name, func() {
|
||||||
ctx, flush := tester.NewContext(suite.T())
|
ctx, flush := tester.NewContext(s.T())
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
bts, err := eventToJSON(tt.event())
|
bts, err := eventToJSON(tt.event())
|
||||||
require.NoError(suite.T(), err, "getting serialized content")
|
require.NoError(s.T(), err, "getting serialized content")
|
||||||
|
|
||||||
out, err := FromJSON(ctx, bts)
|
out, err := FromJSON(ctx, bts)
|
||||||
require.NoError(suite.T(), err, "converting to ics")
|
require.NoError(s.T(), err, "converting to ics")
|
||||||
|
|
||||||
tt.check(out)
|
tt.check(out)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ICSUnitSuite) TestGetRecurrenceTimezone() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
intz string
|
||||||
|
outtz string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
intz: "",
|
||||||
|
outtz: "UTC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "utc",
|
||||||
|
intz: "UTC",
|
||||||
|
outtz: "UTC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
intz: "Asia/Kolkata",
|
||||||
|
outtz: "Asia/Kolkata",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "windows tz",
|
||||||
|
intz: "India Standard Time",
|
||||||
|
outtz: "Asia/Kolkata",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non canonical",
|
||||||
|
intz: "Asia/Calcutta",
|
||||||
|
outtz: "Asia/Kolkata",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range table {
|
||||||
|
s.Run(tt.name, func() {
|
||||||
|
ctx, flush := tester.NewContext(s.T())
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
event := baseEvent()
|
||||||
|
if len(tt.intz) > 0 {
|
||||||
|
recur := models.NewPatternedRecurrence()
|
||||||
|
rp := models.NewRecurrenceRange()
|
||||||
|
rp.SetRecurrenceTimeZone(ptr.To(tt.intz))
|
||||||
|
|
||||||
|
recur.SetRangeEscaped(rp)
|
||||||
|
event.SetRecurrence(recur)
|
||||||
|
}
|
||||||
|
|
||||||
|
timezone, err := getRecurrenceTimezone(ctx, event)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
assert.Equal(s.T(), tt.outtz, timezone.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICSUnitSuite) TestAddTimezoneComponents() {
|
||||||
|
event := baseEvent()
|
||||||
|
recur := models.NewPatternedRecurrence()
|
||||||
|
rp := models.NewRecurrenceRange()
|
||||||
|
rp.SetRecurrenceTimeZone(ptr.To("Asia/Kolkata"))
|
||||||
|
|
||||||
|
recur.SetRangeEscaped(rp)
|
||||||
|
event.SetRecurrence(recur)
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(s.T())
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
cal := ics.NewCalendar()
|
||||||
|
|
||||||
|
err := addTimeZoneComponents(ctx, cal, event)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
text := cal.Serialize()
|
||||||
|
assert.Contains(s.T(), text, "BEGIN:VTIMEZONE", "beginning of timezone")
|
||||||
|
assert.Contains(s.T(), text, "TZID:Asia/Kolkata", "timezone id")
|
||||||
|
assert.Contains(s.T(), text, "END:VTIMEZONE", "end of timezone")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ICSUnitSuite) TestAddTime() {
|
||||||
|
locak, err := time.LoadLocation("Asia/Kolkata")
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
prop ics.ComponentProperty
|
||||||
|
time time.Time
|
||||||
|
allDay bool
|
||||||
|
loc *time.Location
|
||||||
|
exp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "utc",
|
||||||
|
prop: ics.ComponentPropertyDtStart,
|
||||||
|
time: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
allDay: false,
|
||||||
|
loc: time.UTC,
|
||||||
|
exp: "DTSTART:20210102T030405Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "local",
|
||||||
|
prop: ics.ComponentPropertyDtStart,
|
||||||
|
time: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
allDay: false,
|
||||||
|
loc: locak,
|
||||||
|
exp: "DTSTART;TZID=Asia/Kolkata:20210102T083405",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all day",
|
||||||
|
prop: ics.ComponentPropertyDtStart,
|
||||||
|
time: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
allDay: true,
|
||||||
|
loc: time.UTC,
|
||||||
|
exp: "DTSTART;VALUE=DATE:20210102",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all day local",
|
||||||
|
prop: ics.ComponentPropertyDtStart,
|
||||||
|
time: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||||
|
allDay: true,
|
||||||
|
loc: locak,
|
||||||
|
exp: "DTSTART;VALUE=DATE;TZID=Asia/Kolkata:20210102",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "end",
|
||||||
|
prop: ics.ComponentPropertyDtEnd,
|
||||||
|
time: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
allDay: false,
|
||||||
|
loc: time.UTC,
|
||||||
|
exp: "DTEND:20210102T030405Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// This won't happen, but a good test to have to test loc handling
|
||||||
|
name: "windows tz",
|
||||||
|
prop: ics.ComponentPropertyDtStart,
|
||||||
|
time: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
allDay: false,
|
||||||
|
loc: time.FixedZone("India Standard Time", 5*60*60+30*60),
|
||||||
|
exp: "DTSTART;TZID=India Standard Time:20210102T083405",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range table {
|
||||||
|
s.Run(tt.name, func() {
|
||||||
|
cal := ics.NewCalendar()
|
||||||
|
evt := cal.AddEvent("id")
|
||||||
|
|
||||||
|
addTime(evt, tt.prop, tt.time, tt.allDay, tt.loc)
|
||||||
|
|
||||||
|
expSplits := strings.FieldsFunc(tt.exp, func(c rune) bool {
|
||||||
|
return c == ':' || c == ';'
|
||||||
|
})
|
||||||
|
|
||||||
|
text := cal.Serialize()
|
||||||
|
checkLine := ""
|
||||||
|
|
||||||
|
for _, l := range strings.Split(text, "\r\n") {
|
||||||
|
if strings.HasPrefix(l, string(tt.prop)) {
|
||||||
|
checkLine = l
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actSplits := strings.FieldsFunc(checkLine, func(c rune) bool {
|
||||||
|
return c == ':' || c == ';'
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Greater(s.T(), len(checkLine), 0, "line not found")
|
||||||
|
assert.Equal(s.T(), len(expSplits), len(actSplits), "length of fields")
|
||||||
|
assert.ElementsMatch(s.T(), expSplits, actSplits, "fields")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tests and ensures that the generated data is int he format
|
||||||
|
// that we expect
|
||||||
|
func (s *ICSUnitSuite) TestGetTZDataKeyValues() {
|
||||||
|
for key := range tzdata.TZData {
|
||||||
|
s.Run(key, func() {
|
||||||
|
ctx, flush := tester.NewContext(s.T())
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
data, err := getTZDataKeyValues(ctx, key)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
assert.NotEmpty(s.T(), data, "data")
|
||||||
|
assert.NotContains(s.T(), data, "BEGIN", "beginning of timezone") // should be stripped
|
||||||
|
assert.NotContains(s.T(), data, "END", "end of timezone") // should be stripped
|
||||||
|
assert.NotContains(s.T(), data, "TZID", "timezone id") // should be stripped
|
||||||
|
assert.Contains(s.T(), data, "DTSTART", "start time")
|
||||||
|
assert.Contains(s.T(), data, "TZOFFSETFROM", "offset from")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
2796
src/internal/converters/ics/tzdata/data.go
Normal file
2796
src/internal/converters/ics/tzdata/data.go
Normal file
File diff suppressed because it is too large
Load Diff
35
src/internal/converters/ics/tzdata/fetch.sh
Executable file
35
src/internal/converters/ics/tzdata/fetch.sh
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
if ! echo "$PWD" | grep -q '/tzdata$'; then
|
||||||
|
echo "Please run this script from the tzdata dir"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# TODO: Generate from https://www.iana.org/time-zones
|
||||||
|
if [ ! -d /tmp/corso-tzdata ]; then
|
||||||
|
git clone --depth 1 https://github.com/add2cal/timezones-ical-library.git /tmp/corso-tzdata
|
||||||
|
else
|
||||||
|
cd /tmp/corso-tzdata
|
||||||
|
git pull
|
||||||
|
cd -
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate a huge go file with all the timezones
|
||||||
|
echo "package tzdata" >data.go
|
||||||
|
echo "" >>data.go
|
||||||
|
|
||||||
|
echo "var TZData = map[string]string{" >>data.go
|
||||||
|
|
||||||
|
find /tmp/corso-tzdata/ -name '*.ics' | while read -r f; do
|
||||||
|
tz=$(echo "$f" | sed 's|/tmp/corso-tzdata/api/||;s|\.ics$||')
|
||||||
|
echo "Processing $tz"
|
||||||
|
printf "\t\"%s\": \`" "$tz" >>data.go
|
||||||
|
cat "$f" | grep -Ev "(BEGIN:|END:|TZID:)" |
|
||||||
|
sed 's|`|\\`|g;s|\r||;s|TZID:/timezones-ical-library/|TZID:|' |
|
||||||
|
perl -pe 'chomp if eof' >>data.go
|
||||||
|
echo "\`," >>data.go
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "}" >>data.go
|
||||||
@ -59,6 +59,15 @@ const (
|
|||||||
minEpochDurationUpperBound = 7 * 24 * time.Hour
|
minEpochDurationUpperBound = 7 * 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// allValidCompressors is the set of compression algorithms either currently
|
||||||
|
// being used or that were previously used. Use this during the config verify
|
||||||
|
// command to avoid spurious errors. We can revisit whether we want to update
|
||||||
|
// the config in those old repos at a later time.
|
||||||
|
var allValidCompressors = map[compression.Name]struct{}{
|
||||||
|
compression.Name(defaultCompressor): {},
|
||||||
|
compression.Name("s2-default"): {},
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrSettingDefaultConfig = clues.New("setting default repo config values")
|
ErrSettingDefaultConfig = clues.New("setting default repo config values")
|
||||||
ErrorRepoAlreadyExists = clues.New("repo already exists")
|
ErrorRepoAlreadyExists = clues.New("repo already exists")
|
||||||
@ -768,7 +777,7 @@ func (w *conn) verifyDefaultPolicyConfigOptions(
|
|||||||
|
|
||||||
ctx = clues.Add(ctx, "current_global_policy", globalPol.String())
|
ctx = clues.Add(ctx, "current_global_policy", globalPol.String())
|
||||||
|
|
||||||
if globalPol.CompressionPolicy.CompressorName != defaultCompressor {
|
if _, ok := allValidCompressors[globalPol.CompressionPolicy.CompressorName]; !ok {
|
||||||
errs.AddAlert(ctx, fault.NewAlert(
|
errs.AddAlert(ctx, fault.NewAlert(
|
||||||
"unexpected compressor",
|
"unexpected compressor",
|
||||||
corsoWrapperAlertNamespace,
|
corsoWrapperAlertNamespace,
|
||||||
|
|||||||
@ -891,6 +891,20 @@ func (suite *ConnRetentionIntegrationSuite) TestVerifyDefaultConfigOptions() {
|
|||||||
},
|
},
|
||||||
expectAlerts: 1,
|
expectAlerts: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "OldValidCompressor",
|
||||||
|
setupRepo: func(ctx context.Context, t *testing.T, con *conn) {
|
||||||
|
pol, err := con.getGlobalPolicyOrEmpty(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
_, err = updateCompressionOnPolicy("s2-default", pol)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
err = con.writeGlobalPolicy(ctx, "test", pol)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
},
|
||||||
|
expectAlerts: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "NonDefaultCompression",
|
name: "NonDefaultCompression",
|
||||||
setupRepo: func(ctx context.Context, t *testing.T, con *conn) {
|
setupRepo: func(ctx context.Context, t *testing.T, con *conn) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package m365
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
@ -109,7 +110,7 @@ func (ctrl *Controller) ProduceBackupCollections(
|
|||||||
handler = teamschats.NewBackup()
|
handler = teamschats.NewBackup()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, false, clues.Wrap(clues.NewWC(ctx, service.String()), "service not supported")
|
return nil, nil, false, clues.NewWC(ctx, fmt.Sprintf("service not supported: %s", service.HumanString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
colls, excludeItems, canUsePreviousBackup, err = handler.ProduceBackupCollections(
|
colls, excludeItems, canUsePreviousBackup, err = handler.ProduceBackupCollections(
|
||||||
@ -173,7 +174,8 @@ func verifyBackupInputs(sel selectors.Selector, cachedIDs []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !filters.Contains(ids).Compare(sel.ID()) {
|
if !filters.Contains(ids).Compare(sel.ID()) {
|
||||||
return clues.Stack(core.ErrNotFound).With("selector_protected_resource", sel.ID())
|
return clues.Wrap(core.ErrNotFound, "verifying existence of resource").
|
||||||
|
With("selector_protected_resource", sel.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
inMock "github.com/alcionai/corso/src/internal/common/idname/mock"
|
inMock "github.com/alcionai/corso/src/internal/common/idname/mock"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/data/mock"
|
"github.com/alcionai/corso/src/internal/data/mock"
|
||||||
"github.com/alcionai/corso/src/internal/m365/service/exchange"
|
"github.com/alcionai/corso/src/internal/m365/service/exchange"
|
||||||
@ -19,6 +18,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/service/sharepoint"
|
"github.com/alcionai/corso/src/internal/m365/service/sharepoint"
|
||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
@ -36,10 +36,7 @@ import (
|
|||||||
|
|
||||||
type DataCollectionIntgSuite struct {
|
type DataCollectionIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
user string
|
m365 its.M365IntgTestSetup
|
||||||
site string
|
|
||||||
tenantID string
|
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDataCollectionIntgSuite(t *testing.T) {
|
func TestDataCollectionIntgSuite(t *testing.T) {
|
||||||
@ -51,29 +48,14 @@ func TestDataCollectionIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DataCollectionIntgSuite) SetupSuite() {
|
func (suite *DataCollectionIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
|
||||||
suite.user = tconfig.M365UserID(t)
|
|
||||||
suite.site = tconfig.M365SiteID(t)
|
|
||||||
|
|
||||||
acct := tconfig.NewM365Account(t)
|
|
||||||
creds, err := acct.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.tenantID = creds.AzureTenantID
|
|
||||||
|
|
||||||
suite.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *DataCollectionIntgSuite) TestExchangeDataCollection() {
|
func (suite *DataCollectionIntgSuite) TestExchangeDataCollection() {
|
||||||
ctx, flush := tester.NewContext(suite.T())
|
ctx, flush := tester.NewContext(suite.T())
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
selUsers := []string{suite.user}
|
selUsers := []string{suite.m365.User.ID}
|
||||||
|
|
||||||
ctrl := newController(ctx, suite.T(), path.ExchangeService)
|
ctrl := newController(ctx, suite.T(), path.ExchangeService)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -85,7 +67,7 @@ func (suite *DataCollectionIntgSuite) TestExchangeDataCollection() {
|
|||||||
getSelector: func(t *testing.T) selectors.Selector {
|
getSelector: func(t *testing.T) selectors.Selector {
|
||||||
sel := selectors.NewExchangeBackup(selUsers)
|
sel := selectors.NewExchangeBackup(selUsers)
|
||||||
sel.Include(sel.MailFolders([]string{api.MailInbox}, selectors.PrefixMatch()))
|
sel.Include(sel.MailFolders([]string{api.MailInbox}, selectors.PrefixMatch()))
|
||||||
sel.DiscreteOwner = suite.user
|
sel.DiscreteOwner = suite.m365.User.ID
|
||||||
return sel.Selector
|
return sel.Selector
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -94,7 +76,7 @@ func (suite *DataCollectionIntgSuite) TestExchangeDataCollection() {
|
|||||||
getSelector: func(t *testing.T) selectors.Selector {
|
getSelector: func(t *testing.T) selectors.Selector {
|
||||||
sel := selectors.NewExchangeBackup(selUsers)
|
sel := selectors.NewExchangeBackup(selUsers)
|
||||||
sel.Include(sel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()))
|
sel.Include(sel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()))
|
||||||
sel.DiscreteOwner = suite.user
|
sel.DiscreteOwner = suite.m365.User.ID
|
||||||
return sel.Selector
|
return sel.Selector
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -142,8 +124,8 @@ func (suite *DataCollectionIntgSuite) TestExchangeDataCollection() {
|
|||||||
collections, excludes, canUsePreviousBackup, err := exchange.NewBackup().ProduceBackupCollections(
|
collections, excludes, canUsePreviousBackup, err := exchange.NewBackup().ProduceBackupCollections(
|
||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
suite.ac,
|
suite.m365.AC,
|
||||||
suite.ac.Credentials,
|
suite.m365.Creds,
|
||||||
ctrl.UpdateStatus,
|
ctrl.UpdateStatus,
|
||||||
count.New(),
|
count.New(),
|
||||||
fault.New(true))
|
fault.New(true))
|
||||||
@ -270,7 +252,7 @@ func (suite *DataCollectionIntgSuite) TestSharePointDataCollection() {
|
|||||||
ctx, flush := tester.NewContext(suite.T())
|
ctx, flush := tester.NewContext(suite.T())
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
selSites := []string{suite.site}
|
selSites := []string{suite.m365.Site.ID}
|
||||||
ctrl := newController(ctx, suite.T(), path.SharePointService)
|
ctrl := newController(ctx, suite.T(), path.SharePointService)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -312,7 +294,7 @@ func (suite *DataCollectionIntgSuite) TestSharePointDataCollection() {
|
|||||||
collections, excludes, canUsePreviousBackup, err := sharepoint.NewBackup().ProduceBackupCollections(
|
collections, excludes, canUsePreviousBackup, err := sharepoint.NewBackup().ProduceBackupCollections(
|
||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
suite.ac,
|
suite.m365.AC,
|
||||||
ctrl.credentials,
|
ctrl.credentials,
|
||||||
ctrl.UpdateStatus,
|
ctrl.UpdateStatus,
|
||||||
count.New(),
|
count.New(),
|
||||||
@ -351,8 +333,7 @@ func (suite *DataCollectionIntgSuite) TestSharePointDataCollection() {
|
|||||||
|
|
||||||
type SPCollectionIntgSuite struct {
|
type SPCollectionIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
connector *Controller
|
m365 its.M365IntgTestSetup
|
||||||
user string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSPCollectionIntgSuite(t *testing.T) {
|
func TestSPCollectionIntgSuite(t *testing.T) {
|
||||||
@ -364,13 +345,7 @@ func TestSPCollectionIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SPCollectionIntgSuite) SetupSuite() {
|
func (suite *SPCollectionIntgSuite) SetupSuite() {
|
||||||
ctx, flush := tester.NewContext(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
defer flush()
|
|
||||||
|
|
||||||
suite.connector = newController(ctx, suite.T(), path.SharePointService)
|
|
||||||
suite.user = tconfig.M365UserID(suite.T())
|
|
||||||
|
|
||||||
tester.LogTimeOfTest(suite.T())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Libraries() {
|
func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Libraries() {
|
||||||
@ -379,25 +354,20 @@ func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Libraries() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
ctrl := newController(ctx, t, path.SharePointService)
|
||||||
siteID = tconfig.M365SiteID(t)
|
|
||||||
ctrl = newController(ctx, t, path.SharePointService)
|
|
||||||
siteIDs = []string{siteID}
|
|
||||||
)
|
|
||||||
|
|
||||||
site, err := ctrl.PopulateProtectedResourceIDAndName(ctx, siteID, nil)
|
_, err := ctrl.PopulateProtectedResourceIDAndName(ctx, suite.m365.Site.ID, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
sel := selectors.NewSharePointBackup(siteIDs)
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
sel.Include(sel.LibraryFolders([]string{"foo"}, selectors.PrefixMatch()))
|
sel.Include(sel.LibraryFolders([]string{"foo"}, selectors.PrefixMatch()))
|
||||||
sel.Include(sel.Library("Documents"))
|
sel.Include(sel.Library("Documents"))
|
||||||
|
sel.SetDiscreteOwnerIDName(suite.m365.Site.ID, suite.m365.Site.WebURL)
|
||||||
sel.SetDiscreteOwnerIDName(site.ID(), site.Name())
|
|
||||||
|
|
||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: site,
|
ProtectedResource: suite.m365.Site.Provider,
|
||||||
Selector: sel.Selector,
|
Selector: sel.Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,15 +385,15 @@ func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Libraries() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
documentsColl, err := path.BuildPrefix(
|
documentsColl, err := path.BuildPrefix(
|
||||||
suite.connector.tenant,
|
suite.m365.TenantID,
|
||||||
siteID,
|
suite.m365.Site.ID,
|
||||||
path.SharePointService,
|
path.SharePointService,
|
||||||
path.LibrariesCategory)
|
path.LibrariesCategory)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
metadataColl, err := path.BuildMetadata(
|
metadataColl, err := path.BuildMetadata(
|
||||||
suite.connector.tenant,
|
suite.m365.TenantID,
|
||||||
siteID,
|
suite.m365.Site.ID,
|
||||||
path.SharePointService,
|
path.SharePointService,
|
||||||
path.LibrariesCategory,
|
path.LibrariesCategory,
|
||||||
false)
|
false)
|
||||||
@ -450,24 +420,19 @@ func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Lists() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
ctrl := newController(ctx, t, path.SharePointService)
|
||||||
siteID = tconfig.M365SiteID(t)
|
|
||||||
ctrl = newController(ctx, t, path.SharePointService)
|
|
||||||
siteIDs = []string{siteID}
|
|
||||||
)
|
|
||||||
|
|
||||||
site, err := ctrl.PopulateProtectedResourceIDAndName(ctx, siteID, nil)
|
_, err := ctrl.PopulateProtectedResourceIDAndName(ctx, suite.m365.Site.ID, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
sel := selectors.NewSharePointBackup(siteIDs)
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
sel.Include(sel.Lists(selectors.Any()))
|
sel.Include(sel.Lists(selectors.Any()))
|
||||||
|
sel.SetDiscreteOwnerIDName(suite.m365.Site.ID, suite.m365.Site.WebURL)
|
||||||
sel.SetDiscreteOwnerIDName(site.ID(), site.Name())
|
|
||||||
|
|
||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: site,
|
ProtectedResource: suite.m365.Site.Provider,
|
||||||
Selector: sel.Selector,
|
Selector: sel.Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,9 +467,7 @@ func (suite *SPCollectionIntgSuite) TestCreateSharePointCollection_Lists() {
|
|||||||
|
|
||||||
type GroupsCollectionIntgSuite struct {
|
type GroupsCollectionIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
connector *Controller
|
m365 its.M365IntgTestSetup
|
||||||
tenantID string
|
|
||||||
user string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupsCollectionIntgSuite(t *testing.T) {
|
func TestGroupsCollectionIntgSuite(t *testing.T) {
|
||||||
@ -516,21 +479,7 @@ func TestGroupsCollectionIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsCollectionIntgSuite) SetupSuite() {
|
func (suite *GroupsCollectionIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
suite.connector = newController(ctx, t, path.GroupsService)
|
|
||||||
suite.user = tconfig.M365UserID(t)
|
|
||||||
|
|
||||||
acct := tconfig.NewM365Account(t)
|
|
||||||
creds, err := acct.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.tenantID = creds.AzureTenantID
|
|
||||||
|
|
||||||
tester.LogTimeOfTest(t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint() {
|
func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint() {
|
||||||
@ -539,24 +488,19 @@ func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint()
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
ctrl := newController(ctx, t, path.GroupsService)
|
||||||
groupID = tconfig.M365TeamID(t)
|
|
||||||
ctrl = newController(ctx, t, path.GroupsService)
|
|
||||||
groupIDs = []string{groupID}
|
|
||||||
)
|
|
||||||
|
|
||||||
group, err := ctrl.PopulateProtectedResourceIDAndName(ctx, groupID, nil)
|
_, err := ctrl.PopulateProtectedResourceIDAndName(ctx, suite.m365.Group.ID, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
sel := selectors.NewGroupsBackup(groupIDs)
|
sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
|
||||||
sel.Include(sel.LibraryFolders([]string{"test"}, selectors.PrefixMatch()))
|
sel.Include(sel.LibraryFolders([]string{"test"}, selectors.PrefixMatch()))
|
||||||
|
sel.SetDiscreteOwnerIDName(suite.m365.Group.ID, suite.m365.Group.DisplayName)
|
||||||
sel.SetDiscreteOwnerIDName(group.ID(), group.Name())
|
|
||||||
|
|
||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: group,
|
ProtectedResource: suite.m365.Group.Provider,
|
||||||
Selector: sel.Selector,
|
Selector: sel.Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,8 +519,8 @@ func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint()
|
|||||||
assert.Greater(t, len(collections), 1)
|
assert.Greater(t, len(collections), 1)
|
||||||
|
|
||||||
p, err := path.BuildMetadata(
|
p, err := path.BuildMetadata(
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
groupID,
|
suite.m365.Group.ID,
|
||||||
path.GroupsService,
|
path.GroupsService,
|
||||||
path.LibrariesCategory,
|
path.LibrariesCategory,
|
||||||
false)
|
false)
|
||||||
@ -614,31 +558,23 @@ func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint_In
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
ctrl := newController(ctx, t, path.GroupsService)
|
||||||
groupID = tconfig.M365TeamID(t)
|
|
||||||
ctrl = newController(ctx, t, path.GroupsService)
|
|
||||||
groupIDs = []string{groupID}
|
|
||||||
)
|
|
||||||
|
|
||||||
group, err := ctrl.PopulateProtectedResourceIDAndName(ctx, groupID, nil)
|
_, err := ctrl.PopulateProtectedResourceIDAndName(ctx, suite.m365.Group.ID, nil)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
sel := selectors.NewGroupsBackup(groupIDs)
|
sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
|
||||||
sel.Include(sel.LibraryFolders([]string{"test"}, selectors.PrefixMatch()))
|
sel.Include(sel.LibraryFolders([]string{"test"}, selectors.PrefixMatch()))
|
||||||
|
sel.SetDiscreteOwnerIDName(suite.m365.Group.ID, suite.m365.Group.DisplayName)
|
||||||
sel.SetDiscreteOwnerIDName(group.ID(), group.Name())
|
|
||||||
|
|
||||||
site, err := suite.connector.AC.Groups().GetRootSite(ctx, groupID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
pth, err := path.Build(
|
pth, err := path.Build(
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
groupID,
|
suite.m365.Group.ID,
|
||||||
path.GroupsService,
|
path.GroupsService,
|
||||||
path.LibrariesCategory,
|
path.LibrariesCategory,
|
||||||
true,
|
true,
|
||||||
odConsts.SitesPathDir,
|
odConsts.SitesPathDir,
|
||||||
ptr.Val(site.GetId()))
|
suite.m365.Group.RootSite.ID)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
mmc := []data.RestoreCollection{
|
mmc := []data.RestoreCollection{
|
||||||
@ -656,7 +592,7 @@ func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint_In
|
|||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: group,
|
ProtectedResource: suite.m365.Group.Provider,
|
||||||
Selector: sel.Selector,
|
Selector: sel.Selector,
|
||||||
MetadataCollections: mmc,
|
MetadataCollections: mmc,
|
||||||
}
|
}
|
||||||
@ -676,8 +612,8 @@ func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint_In
|
|||||||
assert.Greater(t, len(collections), 1)
|
assert.Greater(t, len(collections), 1)
|
||||||
|
|
||||||
p, err := path.BuildMetadata(
|
p, err := path.BuildMetadata(
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
groupID,
|
suite.m365.Group.ID,
|
||||||
path.GroupsService,
|
path.GroupsService,
|
||||||
path.LibrariesCategory,
|
path.LibrariesCategory,
|
||||||
false)
|
false)
|
||||||
@ -690,13 +626,13 @@ func (suite *GroupsCollectionIntgSuite) TestCreateGroupsCollection_SharePoint_In
|
|||||||
foundRootTombstone := false
|
foundRootTombstone := false
|
||||||
|
|
||||||
sp, err := path.BuildPrefix(
|
sp, err := path.BuildPrefix(
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
groupID,
|
suite.m365.Group.ID,
|
||||||
path.GroupsService,
|
path.GroupsService,
|
||||||
path.LibrariesCategory)
|
path.LibrariesCategory)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
sp, err = sp.Append(false, odConsts.SitesPathDir, ptr.Val(site.GetId()))
|
sp, err = sp.Append(false, odConsts.SitesPathDir, suite.m365.Group.RootSite.ID)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
for _, coll := range collections {
|
for _, coll := range collections {
|
||||||
|
|||||||
@ -366,7 +366,7 @@ func downloadContent(
|
|||||||
itemID := ptr.Val(item.GetId())
|
itemID := ptr.Val(item.GetId())
|
||||||
ctx = clues.Add(ctx, "item_id", itemID)
|
ctx = clues.Add(ctx, "item_id", itemID)
|
||||||
|
|
||||||
content, err := downloadItem(ctx, iaag, item)
|
content, err := downloadItem(ctx, iaag, driveID, item)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return content, nil
|
return content, nil
|
||||||
} else if !graph.IsErrUnauthorizedOrBadToken(err) {
|
} else if !graph.IsErrUnauthorizedOrBadToken(err) {
|
||||||
@ -395,7 +395,7 @@ func downloadContent(
|
|||||||
|
|
||||||
cdi := custom.ToCustomDriveItem(di)
|
cdi := custom.ToCustomDriveItem(di)
|
||||||
|
|
||||||
content, err = downloadItem(ctx, iaag, cdi)
|
content, err = downloadItem(ctx, iaag, driveID, cdi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, clues.Wrap(err, "content download retry")
|
return nil, clues.Wrap(err, "content download retry")
|
||||||
}
|
}
|
||||||
@ -426,7 +426,7 @@ func readItemContents(
|
|||||||
return nil, core.ErrNotFound
|
return nil, core.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, err := downloadFile(ctx, iaag, props.downloadURL)
|
rc, err := downloadFile(ctx, iaag, props.downloadURL, false)
|
||||||
if graph.IsErrUnauthorizedOrBadToken(err) {
|
if graph.IsErrUnauthorizedOrBadToken(err) {
|
||||||
logger.CtxErr(ctx, err).Debug("stale item in cache")
|
logger.CtxErr(ctx, err).Debug("stale item in cache")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,6 @@ import (
|
|||||||
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
bupMD "github.com/alcionai/corso/src/pkg/backup/metadata"
|
bupMD "github.com/alcionai/corso/src/pkg/backup/metadata"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
@ -41,50 +39,6 @@ import (
|
|||||||
|
|
||||||
const defaultFileSize int64 = 42
|
const defaultFileSize int64 = 42
|
||||||
|
|
||||||
// TODO(ashmrtn): Merge with similar structs in graph and exchange packages.
|
|
||||||
type oneDriveService struct {
|
|
||||||
credentials account.M365Config
|
|
||||||
status support.ControllerOperationStatus
|
|
||||||
ac api.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func newOneDriveService(credentials account.M365Config) (*oneDriveService, error) {
|
|
||||||
ac, err := api.NewClient(
|
|
||||||
credentials,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
service := oneDriveService{
|
|
||||||
ac: ac,
|
|
||||||
credentials: credentials,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &service, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ods *oneDriveService) updateStatus(status *support.ControllerOperationStatus) {
|
|
||||||
if status == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ods.status = support.MergeStatus(ods.status, *status)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadTestService(t *testing.T) *oneDriveService {
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
|
|
||||||
creds, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
service, err := newOneDriveService(creds)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
return service
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// collections
|
// collections
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -841,7 +795,12 @@ func (h mockBackupHandler[T]) AugmentItemInfo(
|
|||||||
return h.ItemInfo
|
return h.ItemInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *mockBackupHandler[T]) Get(context.Context, string, map[string]string) (*http.Response, error) {
|
func (h *mockBackupHandler[T]) Get(
|
||||||
|
context.Context,
|
||||||
|
string,
|
||||||
|
map[string]string,
|
||||||
|
bool,
|
||||||
|
) (*http.Response, error) {
|
||||||
c := h.getCall
|
c := h.getCall
|
||||||
h.getCall++
|
h.getCall++
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
acceptHeaderKey = "Accept"
|
acceptHeaderKey = "Accept"
|
||||||
acceptHeaderValue = "*/*"
|
acceptHeaderValue = "*/*"
|
||||||
|
gigabyte = 1024 * 1024 * 1024
|
||||||
|
largeFileDownloadLimit = 15 * gigabyte
|
||||||
)
|
)
|
||||||
|
|
||||||
// downloadUrlKeys is used to find the download URL in a DriveItem response.
|
// downloadUrlKeys is used to find the download URL in a DriveItem response.
|
||||||
@ -33,7 +35,8 @@ var downloadURLKeys = []string{
|
|||||||
|
|
||||||
func downloadItem(
|
func downloadItem(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
ag api.Getter,
|
getter api.Getter,
|
||||||
|
driveID string,
|
||||||
item *custom.DriveItem,
|
item *custom.DriveItem,
|
||||||
) (io.ReadCloser, error) {
|
) (io.ReadCloser, error) {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
@ -41,35 +44,36 @@ func downloadItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rc io.ReadCloser
|
// very large file content needs to be downloaded through a different endpoint, or else
|
||||||
isFile = item.GetFile() != nil
|
// the download could take longer than the lifespan of the download token in the cached
|
||||||
|
// url, which will cause us to timeout on every download request, even if we refresh the
|
||||||
|
// download url right before the query.
|
||||||
|
url = "https://graph.microsoft.com/v1.0/drives/" + driveID + "/items/" + ptr.Val(item.GetId()) + "/content"
|
||||||
|
reader io.ReadCloser
|
||||||
err error
|
err error
|
||||||
|
isLargeFile = ptr.Val(item.GetSize()) > largeFileDownloadLimit
|
||||||
)
|
)
|
||||||
|
|
||||||
if isFile {
|
// if this isn't a file, no content is available for download
|
||||||
var (
|
if item.GetFile() == nil {
|
||||||
url string
|
return reader, nil
|
||||||
ad = item.GetAdditionalData()
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, key := range downloadURLKeys {
|
|
||||||
if v, err := str.AnyValueToString(key, ad); err == nil {
|
|
||||||
url = v
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, err = downloadFile(ctx, ag, url)
|
// smaller files will maintain our current behavior (prefetching the download url with the
|
||||||
if err != nil {
|
// url cache). That pattern works for us in general, and we only need to deviate for very
|
||||||
return nil, clues.Stack(err)
|
// large file sizes.
|
||||||
}
|
if !isLargeFile {
|
||||||
|
url = str.FirstIn(item.GetAdditionalData(), downloadURLKeys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc, nil
|
reader, err = downloadFile(ctx, getter, url, isLargeFile)
|
||||||
|
|
||||||
|
return reader, clues.StackWC(ctx, err).OrNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
type downloadWithRetries struct {
|
type downloadWithRetries struct {
|
||||||
getter api.Getter
|
getter api.Getter
|
||||||
|
requireAuth bool
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +90,7 @@ func (dg *downloadWithRetries) Get(
|
|||||||
// wouldn't work without it (get 416 responses instead of 206).
|
// wouldn't work without it (get 416 responses instead of 206).
|
||||||
headers[acceptHeaderKey] = acceptHeaderValue
|
headers[acceptHeaderKey] = acceptHeaderValue
|
||||||
|
|
||||||
resp, err := dg.getter.Get(ctx, dg.url, headers)
|
resp, err := dg.getter.Get(ctx, dg.url, headers, dg.requireAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, clues.Wrap(err, "getting file")
|
return nil, clues.Wrap(err, "getting file")
|
||||||
}
|
}
|
||||||
@ -96,7 +100,7 @@ func (dg *downloadWithRetries) Get(
|
|||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, clues.New("malware detected").Label(graph.LabelsMalware)
|
return nil, clues.NewWC(ctx, "malware detected").Label(graph.LabelsMalware)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp != nil && (resp.StatusCode/100) != 2 {
|
if resp != nil && (resp.StatusCode/100) != 2 {
|
||||||
@ -107,7 +111,7 @@ func (dg *downloadWithRetries) Get(
|
|||||||
// upstream error checks can compare the status with
|
// upstream error checks can compare the status with
|
||||||
// clues.HasLabel(err, graph.LabelStatus(http.KnownStatusCode))
|
// clues.HasLabel(err, graph.LabelStatus(http.KnownStatusCode))
|
||||||
return nil, clues.
|
return nil, clues.
|
||||||
Wrap(clues.New(resp.Status), "non-2xx http response").
|
Wrap(clues.NewWC(ctx, resp.Status), "non-2xx http response").
|
||||||
Label(graph.LabelStatus(resp.StatusCode))
|
Label(graph.LabelStatus(resp.StatusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +122,7 @@ func downloadFile(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
ag api.Getter,
|
ag api.Getter,
|
||||||
url string,
|
url string,
|
||||||
|
requireAuth bool,
|
||||||
) (io.ReadCloser, error) {
|
) (io.ReadCloser, error) {
|
||||||
if len(url) == 0 {
|
if len(url) == 0 {
|
||||||
return nil, clues.NewWC(ctx, "empty file url")
|
return nil, clues.NewWC(ctx, "empty file url")
|
||||||
@ -142,6 +147,7 @@ func downloadFile(
|
|||||||
ctx,
|
ctx,
|
||||||
&downloadWithRetries{
|
&downloadWithRetries{
|
||||||
getter: ag,
|
getter: ag,
|
||||||
|
requireAuth: requireAuth,
|
||||||
url: url,
|
url: url,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/internal/common/prefixmatcher"
|
"github.com/alcionai/corso/src/internal/common/prefixmatcher"
|
||||||
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
@ -233,6 +234,18 @@ func (suite *OneDriveIntgSuite) SetupSuite() {
|
|||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stubStatusUpdater struct {
|
||||||
|
status support.ControllerOperationStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ssu *stubStatusUpdater) updateStatus(status *support.ControllerOperationStatus) {
|
||||||
|
if status == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ssu.status = support.MergeStatus(ssu.status, *status)
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *OneDriveIntgSuite) TestOneDriveNewCollections() {
|
func (suite *OneDriveIntgSuite) TestOneDriveNewCollections() {
|
||||||
creds, err := tconfig.NewM365Account(suite.T()).M365Config()
|
creds, err := tconfig.NewM365Account(suite.T()).M365Config()
|
||||||
require.NoError(suite.T(), err, clues.ToCore(err))
|
require.NoError(suite.T(), err, clues.ToCore(err))
|
||||||
@ -258,10 +271,10 @@ func (suite *OneDriveIntgSuite) TestOneDriveNewCollections() {
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
service = loadTestService(t)
|
|
||||||
scope = selectors.
|
scope = selectors.
|
||||||
NewOneDriveBackup([]string{test.user}).
|
NewOneDriveBackup([]string{test.user}).
|
||||||
AllData()[0]
|
AllData()[0]
|
||||||
|
statusUpdater = stubStatusUpdater{}
|
||||||
)
|
)
|
||||||
|
|
||||||
colls := NewCollections(
|
colls := NewCollections(
|
||||||
@ -274,7 +287,7 @@ func (suite *OneDriveIntgSuite) TestOneDriveNewCollections() {
|
|||||||
},
|
},
|
||||||
creds.AzureTenantID,
|
creds.AzureTenantID,
|
||||||
idname.NewProvider(test.user, test.user),
|
idname.NewProvider(test.user, test.user),
|
||||||
service.updateStatus,
|
statusUpdater.updateStatus,
|
||||||
control.Options{
|
control.Options{
|
||||||
ToggleFeatures: control.Toggles{},
|
ToggleFeatures: control.Toggles{},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/common/str"
|
"github.com/alcionai/corso/src/internal/common/str"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
@ -30,9 +31,7 @@ import (
|
|||||||
|
|
||||||
type ItemIntegrationSuite struct {
|
type ItemIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
user string
|
m365 its.M365IntgTestSetup
|
||||||
userDriveID string
|
|
||||||
service *oneDriveService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestItemIntegrationSuite(t *testing.T) {
|
func TestItemIntegrationSuite(t *testing.T) {
|
||||||
@ -44,25 +43,7 @@ func TestItemIntegrationSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ItemIntegrationSuite) SetupSuite() {
|
func (suite *ItemIntegrationSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
suite.service = loadTestService(t)
|
|
||||||
suite.user = tconfig.SecondaryM365UserID(t)
|
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
|
||||||
|
|
||||||
pager := suite.service.ac.Drives().NewUserDrivePager(suite.user, nil)
|
|
||||||
|
|
||||||
odDrives, err := api.GetAllDrives(ctx, pager)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
// Test Requirement 1: Need a drive
|
|
||||||
require.Greaterf(t, len(odDrives), 0, "user %s does not have a drive", suite.user)
|
|
||||||
|
|
||||||
// Pick the first drive
|
|
||||||
suite.userDriveID = ptr.Val(odDrives[0].GetId())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOneDriveItem(
|
func getOneDriveItem(
|
||||||
@ -103,28 +84,36 @@ func (suite *ItemIntegrationSuite) TestItemReader_oneDrive() {
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
sc := selectors.
|
sc := selectors.
|
||||||
NewOneDriveBackup([]string{suite.user}).
|
NewOneDriveBackup([]string{suite.m365.User.ID}).
|
||||||
AllData()[0]
|
AllData()[0]
|
||||||
|
|
||||||
driveItem := getOneDriveItem(ctx, t, suite.service.ac, suite.userDriveID)
|
driveItem := getOneDriveItem(
|
||||||
|
ctx,
|
||||||
|
t,
|
||||||
|
suite.m365.AC,
|
||||||
|
suite.m365.User.DriveID)
|
||||||
// Test Requirement 2: Need a file
|
// Test Requirement 2: Need a file
|
||||||
require.NotEmpty(
|
require.NotEmpty(
|
||||||
t,
|
t,
|
||||||
driveItem,
|
driveItem,
|
||||||
"no file item found for user %s drive %s",
|
"no file item found for user %q drive %q",
|
||||||
suite.user,
|
suite.m365.User.ID,
|
||||||
suite.userDriveID)
|
suite.m365.User.DriveID)
|
||||||
|
|
||||||
bh := &userDriveBackupHandler{
|
bh := &userDriveBackupHandler{
|
||||||
baseUserDriveHandler: baseUserDriveHandler{
|
baseUserDriveHandler: baseUserDriveHandler{
|
||||||
ac: suite.service.ac.Drives(),
|
ac: suite.m365.AC.Drives(),
|
||||||
},
|
},
|
||||||
userID: suite.user,
|
userID: suite.m365.User.ID,
|
||||||
scope: sc,
|
scope: sc,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read data for the file
|
// Read data for the file
|
||||||
itemData, err := downloadItem(ctx, bh, custom.ToCustomDriveItem(driveItem))
|
itemData, err := downloadItem(
|
||||||
|
ctx,
|
||||||
|
bh,
|
||||||
|
suite.m365.User.DriveID,
|
||||||
|
custom.ToCustomDriveItem(driveItem))
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
size, err := io.Copy(io.Discard, itemData)
|
size, err := io.Copy(io.Discard, itemData)
|
||||||
@ -142,13 +131,13 @@ func (suite *ItemIntegrationSuite) TestIsURLExpired() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
driveItem := getOneDriveItem(ctx, t, suite.service.ac, suite.userDriveID)
|
driveItem := getOneDriveItem(ctx, t, suite.m365.AC, suite.m365.User.DriveID)
|
||||||
require.NotEmpty(
|
require.NotEmpty(
|
||||||
t,
|
t,
|
||||||
driveItem,
|
driveItem,
|
||||||
"no file item found for user %s drive %s",
|
"no file item found for user %q drive %q",
|
||||||
suite.user,
|
suite.m365.User.ID,
|
||||||
suite.userDriveID)
|
suite.m365.User.DriveID)
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
|
|
||||||
@ -173,7 +162,7 @@ func (suite *ItemIntegrationSuite) TestItemWriter() {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "",
|
name: "",
|
||||||
driveID: suite.userDriveID,
|
driveID: suite.m365.User.DriveID,
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: "sharePoint",
|
// name: "sharePoint",
|
||||||
@ -183,12 +172,12 @@ func (suite *ItemIntegrationSuite) TestItemWriter() {
|
|||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
rh := NewUserDriveRestoreHandler(suite.service.ac)
|
rh := NewUserDriveRestoreHandler(suite.m365.AC)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
root, err := suite.service.ac.Drives().GetRootFolder(ctx, test.driveID)
|
root, err := suite.m365.AC.Drives().GetRootFolder(ctx, test.driveID)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
newFolderName := testdata.DefaultRestoreConfig("folder").Location
|
newFolderName := testdata.DefaultRestoreConfig("folder").Location
|
||||||
@ -217,7 +206,7 @@ func (suite *ItemIntegrationSuite) TestItemWriter() {
|
|||||||
|
|
||||||
// HACK: Leveraging this to test getFolder behavior for a file. `getFolder()` on the
|
// HACK: Leveraging this to test getFolder behavior for a file. `getFolder()` on the
|
||||||
// newly created item should fail because it's a file not a folder
|
// newly created item should fail because it's a file not a folder
|
||||||
_, err = suite.service.ac.Drives().GetFolderByName(
|
_, err = suite.m365.AC.Drives().GetFolderByName(
|
||||||
ctx,
|
ctx,
|
||||||
test.driveID,
|
test.driveID,
|
||||||
ptr.Val(newFolder.GetId()),
|
ptr.Val(newFolder.GetId()),
|
||||||
@ -261,7 +250,7 @@ func (suite *ItemIntegrationSuite) TestDriveGetFolder() {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "oneDrive",
|
name: "oneDrive",
|
||||||
driveID: suite.userDriveID,
|
driveID: suite.m365.User.DriveID,
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// name: "sharePoint",
|
// name: "sharePoint",
|
||||||
@ -275,11 +264,11 @@ func (suite *ItemIntegrationSuite) TestDriveGetFolder() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
root, err := suite.service.ac.Drives().GetRootFolder(ctx, test.driveID)
|
root, err := suite.m365.AC.Drives().GetRootFolder(ctx, test.driveID)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
// Lookup a folder that doesn't exist
|
// Lookup a folder that doesn't exist
|
||||||
_, err = suite.service.ac.Drives().GetFolderByName(
|
_, err = suite.m365.AC.Drives().GetFolderByName(
|
||||||
ctx,
|
ctx,
|
||||||
test.driveID,
|
test.driveID,
|
||||||
ptr.Val(root.GetId()),
|
ptr.Val(root.GetId()),
|
||||||
@ -287,7 +276,7 @@ func (suite *ItemIntegrationSuite) TestDriveGetFolder() {
|
|||||||
require.ErrorIs(t, err, api.ErrFolderNotFound, clues.ToCore(err))
|
require.ErrorIs(t, err, api.ErrFolderNotFound, clues.ToCore(err))
|
||||||
|
|
||||||
// Lookup a folder that does exist
|
// Lookup a folder that does exist
|
||||||
_, err = suite.service.ac.Drives().GetFolderByName(
|
_, err = suite.m365.AC.Drives().GetFolderByName(
|
||||||
ctx,
|
ctx,
|
||||||
test.driveID,
|
test.driveID,
|
||||||
ptr.Val(root.GetId()),
|
ptr.Val(root.GetId()),
|
||||||
@ -307,6 +296,7 @@ func (m mockGetter) Get(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
url string,
|
url string,
|
||||||
headers map[string]string,
|
headers map[string]string,
|
||||||
|
requireAuth bool,
|
||||||
) (*http.Response, error) {
|
) (*http.Response, error) {
|
||||||
return m.GetFunc(ctx, url)
|
return m.GetFunc(ctx, url)
|
||||||
}
|
}
|
||||||
@ -394,7 +384,7 @@ func (suite *ItemUnitTestSuite) TestDownloadItem() {
|
|||||||
return nil, clues.New("test error")
|
return nil, clues.New("test error")
|
||||||
},
|
},
|
||||||
errorExpected: require.Error,
|
errorExpected: require.Error,
|
||||||
rcExpected: require.Nil,
|
rcExpected: require.NotNil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "download url is empty",
|
name: "download url is empty",
|
||||||
@ -431,7 +421,7 @@ func (suite *ItemUnitTestSuite) TestDownloadItem() {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
errorExpected: require.Error,
|
errorExpected: require.Error,
|
||||||
rcExpected: require.Nil,
|
rcExpected: require.NotNil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-2xx http response",
|
name: "non-2xx http response",
|
||||||
@ -450,7 +440,7 @@ func (suite *ItemUnitTestSuite) TestDownloadItem() {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
errorExpected: require.Error,
|
errorExpected: require.Error,
|
||||||
rcExpected: require.Nil,
|
rcExpected: require.NotNil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,9 +453,78 @@ func (suite *ItemUnitTestSuite) TestDownloadItem() {
|
|||||||
mg := mockGetter{
|
mg := mockGetter{
|
||||||
GetFunc: test.GetFunc,
|
GetFunc: test.GetFunc,
|
||||||
}
|
}
|
||||||
rc, err := downloadItem(ctx, mg, custom.ToCustomDriveItem(test.itemFunc()))
|
rc, err := downloadItem(
|
||||||
|
ctx,
|
||||||
|
mg,
|
||||||
|
"driveID",
|
||||||
|
custom.ToCustomDriveItem(test.itemFunc()))
|
||||||
test.errorExpected(t, err, clues.ToCore(err))
|
test.errorExpected(t, err, clues.ToCore(err))
|
||||||
test.rcExpected(t, rc)
|
test.rcExpected(t, rc, "reader should only be nil if item is nil")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ItemUnitTestSuite) TestDownloadItem_urlByFileSize() {
|
||||||
|
var (
|
||||||
|
testRc = io.NopCloser(bytes.NewReader([]byte("test")))
|
||||||
|
url = "https://example.com"
|
||||||
|
okResp = &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: testRc,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
itemFunc func() models.DriveItemable
|
||||||
|
GetFunc func(ctx context.Context, url string) (*http.Response, error)
|
||||||
|
errorExpected require.ErrorAssertionFunc
|
||||||
|
rcExpected require.ValueAssertionFunc
|
||||||
|
label string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "big file",
|
||||||
|
itemFunc: func() models.DriveItemable {
|
||||||
|
di := api.NewDriveItem("test", false)
|
||||||
|
di.SetAdditionalData(map[string]any{"@microsoft.graph.downloadUrl": url})
|
||||||
|
di.SetSize(ptr.To[int64](20 * gigabyte))
|
||||||
|
|
||||||
|
return di
|
||||||
|
},
|
||||||
|
GetFunc: func(ctx context.Context, url string) (*http.Response, error) {
|
||||||
|
assert.Contains(suite.T(), url, "/content")
|
||||||
|
return okResp, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "small file",
|
||||||
|
itemFunc: func() models.DriveItemable {
|
||||||
|
di := api.NewDriveItem("test", false)
|
||||||
|
di.SetAdditionalData(map[string]any{"@microsoft.graph.downloadUrl": url})
|
||||||
|
di.SetSize(ptr.To[int64](2 * gigabyte))
|
||||||
|
|
||||||
|
return di
|
||||||
|
},
|
||||||
|
GetFunc: func(ctx context.Context, url string) (*http.Response, error) {
|
||||||
|
assert.NotContains(suite.T(), url, "/content")
|
||||||
|
return okResp, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
_, err := downloadItem(
|
||||||
|
ctx,
|
||||||
|
mockGetter{GetFunc: test.GetFunc},
|
||||||
|
"driveID",
|
||||||
|
custom.ToCustomDriveItem(test.itemFunc()))
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -522,7 +581,11 @@ func (suite *ItemUnitTestSuite) TestDownloadItem_ConnectionResetErrorOnFirstRead
|
|||||||
mg := mockGetter{
|
mg := mockGetter{
|
||||||
GetFunc: GetFunc,
|
GetFunc: GetFunc,
|
||||||
}
|
}
|
||||||
rc, err := downloadItem(ctx, mg, custom.ToCustomDriveItem(itemFunc()))
|
rc, err := downloadItem(
|
||||||
|
ctx,
|
||||||
|
mg,
|
||||||
|
"driveID",
|
||||||
|
custom.ToCustomDriveItem(itemFunc()))
|
||||||
errorExpected(t, err, clues.ToCore(err))
|
errorExpected(t, err, clues.ToCore(err))
|
||||||
rcExpected(t, rc)
|
rcExpected(t, rc)
|
||||||
|
|
||||||
|
|||||||
@ -93,8 +93,9 @@ func (h siteBackupHandler) Get(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
url string,
|
url string,
|
||||||
headers map[string]string,
|
headers map[string]string,
|
||||||
|
requireAuth bool,
|
||||||
) (*http.Response, error) {
|
) (*http.Response, error) {
|
||||||
return h.ac.Get(ctx, url, headers)
|
return h.ac.Get(ctx, url, headers, requireAuth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h siteBackupHandler) PathPrefix(
|
func (h siteBackupHandler) PathPrefix(
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
@ -34,9 +35,7 @@ import (
|
|||||||
|
|
||||||
type URLCacheIntegrationSuite struct {
|
type URLCacheIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
ac api.Client
|
m365 its.M365IntgTestSetup
|
||||||
user string
|
|
||||||
driveID string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestURLCacheIntegrationSuite(t *testing.T) {
|
func TestURLCacheIntegrationSuite(t *testing.T) {
|
||||||
@ -49,29 +48,12 @@ func TestURLCacheIntegrationSuite(t *testing.T) {
|
|||||||
|
|
||||||
func (suite *URLCacheIntegrationSuite) SetupSuite() {
|
func (suite *URLCacheIntegrationSuite) SetupSuite() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
||||||
|
|
||||||
suite.user = tconfig.SecondaryM365UserID(t)
|
|
||||||
|
|
||||||
acct := tconfig.NewM365Account(t)
|
|
||||||
|
|
||||||
creds, err := acct.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
drive, err := suite.ac.Users().GetDefaultDrive(ctx, suite.user)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.driveID = ptr.Val(drive.GetId())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic test for urlCache. Create some files in onedrive, then access them via
|
// Basic test for urlCache. Create some files in onedrive, then access them via
|
||||||
@ -79,22 +61,18 @@ func (suite *URLCacheIntegrationSuite) SetupSuite() {
|
|||||||
func (suite *URLCacheIntegrationSuite) TestURLCacheBasic() {
|
func (suite *URLCacheIntegrationSuite) TestURLCacheBasic() {
|
||||||
var (
|
var (
|
||||||
t = suite.T()
|
t = suite.T()
|
||||||
ac = suite.ac.Drives()
|
ac = suite.m365.AC.Drives()
|
||||||
driveID = suite.driveID
|
driveID = suite.m365.User.DriveID
|
||||||
newFolderName = testdata.DefaultRestoreConfig("folder").Location
|
newFolderName = testdata.DefaultRestoreConfig("folder").Location
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
// Create a new test folder
|
|
||||||
root, err := ac.GetRootFolder(ctx, driveID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
newFolder, err := ac.PostItemInContainer(
|
newFolder, err := ac.PostItemInContainer(
|
||||||
ctx,
|
ctx,
|
||||||
driveID,
|
driveID,
|
||||||
ptr.Val(root.GetId()),
|
suite.m365.User.DriveRootFolderID,
|
||||||
api.NewDriveItem(newFolderName, true),
|
api.NewDriveItem(newFolderName, true),
|
||||||
control.Copy)
|
control.Copy)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
@ -105,7 +83,7 @@ func (suite *URLCacheIntegrationSuite) TestURLCacheBasic() {
|
|||||||
// Get the previous delta to feed into url cache
|
// Get the previous delta to feed into url cache
|
||||||
pager := ac.EnumerateDriveItemsDelta(
|
pager := ac.EnumerateDriveItemsDelta(
|
||||||
ctx,
|
ctx,
|
||||||
suite.driveID,
|
driveID,
|
||||||
"",
|
"",
|
||||||
api.CallConfig{
|
api.CallConfig{
|
||||||
Select: api.URLCacheDriveItemProps(),
|
Select: api.URLCacheDriveItemProps(),
|
||||||
@ -142,10 +120,10 @@ func (suite *URLCacheIntegrationSuite) TestURLCacheBasic() {
|
|||||||
|
|
||||||
// Create a new URL cache with a long TTL
|
// Create a new URL cache with a long TTL
|
||||||
uc, err := newURLCache(
|
uc, err := newURLCache(
|
||||||
suite.driveID,
|
driveID,
|
||||||
du.URL,
|
du.URL,
|
||||||
1*time.Hour,
|
1*time.Hour,
|
||||||
suite.ac.Drives(),
|
ac,
|
||||||
count.New(),
|
count.New(),
|
||||||
fault.New(true))
|
fault.New(true))
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
@ -176,7 +154,8 @@ func (suite *URLCacheIntegrationSuite) TestURLCacheBasic() {
|
|||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
props.downloadURL,
|
props.downloadURL,
|
||||||
nil,
|
nil,
|
||||||
nil)
|
nil,
|
||||||
|
false)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
require.NotNil(t, resp)
|
require.NotNil(t, resp)
|
||||||
|
|||||||
@ -93,8 +93,9 @@ func (h userDriveBackupHandler) Get(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
url string,
|
url string,
|
||||||
headers map[string]string,
|
headers map[string]string,
|
||||||
|
requireAuth bool,
|
||||||
) (*http.Response, error) {
|
) (*http.Response, error) {
|
||||||
return h.ac.Get(ctx, url, headers)
|
return h.ac.Get(ctx, url, headers, requireAuth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h userDriveBackupHandler) PathPrefix(
|
func (h userDriveBackupHandler) PathPrefix(
|
||||||
|
|||||||
@ -296,6 +296,7 @@ func populateCollections(
|
|||||||
cl),
|
cl),
|
||||||
qp.ProtectedResource.ID(),
|
qp.ProtectedResource.ID(),
|
||||||
bh.itemHandler(),
|
bh.itemHandler(),
|
||||||
|
bh,
|
||||||
addAndRem.Added,
|
addAndRem.Added,
|
||||||
addAndRem.Removed,
|
addAndRem.Removed,
|
||||||
// TODO: produce a feature flag that allows selective
|
// TODO: produce a feature flag that allows selective
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
@ -87,6 +88,14 @@ func (bh mockBackupHandler) folderGetter() containerGetter { return
|
|||||||
func (bh mockBackupHandler) previewIncludeContainers() []string { return bh.previewIncludes }
|
func (bh mockBackupHandler) previewIncludeContainers() []string { return bh.previewIncludes }
|
||||||
func (bh mockBackupHandler) previewExcludeContainers() []string { return bh.previewExcludes }
|
func (bh mockBackupHandler) previewExcludeContainers() []string { return bh.previewExcludes }
|
||||||
|
|
||||||
|
func (bh mockBackupHandler) CanSkipItemFailure(
|
||||||
|
err error,
|
||||||
|
resourceID string,
|
||||||
|
opts control.Options,
|
||||||
|
) (fault.SkipCause, bool) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
func (bh mockBackupHandler) NewContainerCache(
|
func (bh mockBackupHandler) NewContainerCache(
|
||||||
userID string,
|
userID string,
|
||||||
) (string, graph.ContainerResolver) {
|
) (string, graph.ContainerResolver) {
|
||||||
@ -472,10 +481,7 @@ func newStatusUpdater(t *testing.T, wg *sync.WaitGroup) func(status *support.Con
|
|||||||
|
|
||||||
type BackupIntgSuite struct {
|
type BackupIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
user string
|
m365 its.M365IntgTestSetup
|
||||||
site string
|
|
||||||
tenantID string
|
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupIntgSuite(t *testing.T) {
|
func TestBackupIntgSuite(t *testing.T) {
|
||||||
@ -488,35 +494,18 @@ func TestBackupIntgSuite(t *testing.T) {
|
|||||||
|
|
||||||
func (suite *BackupIntgSuite) SetupSuite() {
|
func (suite *BackupIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
||||||
|
|
||||||
suite.user = tconfig.M365UserID(t)
|
|
||||||
suite.site = tconfig.M365SiteID(t)
|
|
||||||
|
|
||||||
acct := tconfig.NewM365Account(t)
|
|
||||||
creds, err := acct.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.tenantID = creds.AzureTenantID
|
|
||||||
|
|
||||||
tester.LogTimeOfTest(t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupIntgSuite) TestMailFetch() {
|
func (suite *BackupIntgSuite) TestMailFetch() {
|
||||||
var (
|
var (
|
||||||
userID = tconfig.M365UserID(suite.T())
|
users = []string{suite.m365.User.ID}
|
||||||
users = []string{userID}
|
handlers = BackupHandlers(suite.m365.AC)
|
||||||
handlers = BackupHandlers(suite.ac)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -560,14 +549,14 @@ func (suite *BackupIntgSuite) TestMailFetch() {
|
|||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: ctrlOpts,
|
Options: ctrlOpts,
|
||||||
ProtectedResource: inMock.NewProvider(userID, userID),
|
ProtectedResource: suite.m365.User.Provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
collections, err := CreateCollections(
|
collections, err := CreateCollections(
|
||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
handlers,
|
handlers,
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
test.scope,
|
test.scope,
|
||||||
metadata.DeltaPaths{},
|
metadata.DeltaPaths{},
|
||||||
func(status *support.ControllerOperationStatus) {},
|
func(status *support.ControllerOperationStatus) {},
|
||||||
@ -602,9 +591,8 @@ func (suite *BackupIntgSuite) TestMailFetch() {
|
|||||||
|
|
||||||
func (suite *BackupIntgSuite) TestDelta() {
|
func (suite *BackupIntgSuite) TestDelta() {
|
||||||
var (
|
var (
|
||||||
userID = tconfig.M365UserID(suite.T())
|
users = []string{suite.m365.User.ID}
|
||||||
users = []string{userID}
|
handlers = BackupHandlers(suite.m365.AC)
|
||||||
handlers = BackupHandlers(suite.ac)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -640,7 +628,7 @@ func (suite *BackupIntgSuite) TestDelta() {
|
|||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: inMock.NewProvider(userID, userID),
|
ProtectedResource: suite.m365.User.Provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
// get collections without providing any delta history (ie: full backup)
|
// get collections without providing any delta history (ie: full backup)
|
||||||
@ -648,7 +636,7 @@ func (suite *BackupIntgSuite) TestDelta() {
|
|||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
handlers,
|
handlers,
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
test.scope,
|
test.scope,
|
||||||
metadata.DeltaPaths{},
|
metadata.DeltaPaths{},
|
||||||
func(status *support.ControllerOperationStatus) {},
|
func(status *support.ControllerOperationStatus) {},
|
||||||
@ -681,7 +669,7 @@ func (suite *BackupIntgSuite) TestDelta() {
|
|||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
handlers,
|
handlers,
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
test.scope,
|
test.scope,
|
||||||
dps,
|
dps,
|
||||||
func(status *support.ControllerOperationStatus) {},
|
func(status *support.ControllerOperationStatus) {},
|
||||||
@ -703,8 +691,8 @@ func (suite *BackupIntgSuite) TestMailSerializationRegression() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
users = []string{suite.user}
|
users = []string{suite.m365.User.ID}
|
||||||
handlers = BackupHandlers(suite.ac)
|
handlers = BackupHandlers(suite.m365.AC)
|
||||||
)
|
)
|
||||||
|
|
||||||
sel := selectors.NewExchangeBackup(users)
|
sel := selectors.NewExchangeBackup(users)
|
||||||
@ -713,7 +701,7 @@ func (suite *BackupIntgSuite) TestMailSerializationRegression() {
|
|||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: inMock.NewProvider(suite.user, suite.user),
|
ProtectedResource: suite.m365.User.Provider,
|
||||||
Selector: sel.Selector,
|
Selector: sel.Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -721,7 +709,7 @@ func (suite *BackupIntgSuite) TestMailSerializationRegression() {
|
|||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
handlers,
|
handlers,
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
sel.Scopes()[0],
|
sel.Scopes()[0],
|
||||||
metadata.DeltaPaths{},
|
metadata.DeltaPaths{},
|
||||||
newStatusUpdater(t, &wg),
|
newStatusUpdater(t, &wg),
|
||||||
@ -773,8 +761,8 @@ func (suite *BackupIntgSuite) TestMailSerializationRegression() {
|
|||||||
// a regression test to ensure that downloaded items can be uploaded.
|
// a regression test to ensure that downloaded items can be uploaded.
|
||||||
func (suite *BackupIntgSuite) TestContactSerializationRegression() {
|
func (suite *BackupIntgSuite) TestContactSerializationRegression() {
|
||||||
var (
|
var (
|
||||||
users = []string{suite.user}
|
users = []string{suite.m365.User.ID}
|
||||||
handlers = BackupHandlers(suite.ac)
|
handlers = BackupHandlers(suite.m365.AC)
|
||||||
)
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -801,14 +789,14 @@ func (suite *BackupIntgSuite) TestContactSerializationRegression() {
|
|||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: inMock.NewProvider(suite.user, suite.user),
|
ProtectedResource: suite.m365.User.Provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
edcs, err := CreateCollections(
|
edcs, err := CreateCollections(
|
||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
handlers,
|
handlers,
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
test.scope,
|
test.scope,
|
||||||
metadata.DeltaPaths{},
|
metadata.DeltaPaths{},
|
||||||
newStatusUpdater(t, &wg),
|
newStatusUpdater(t, &wg),
|
||||||
@ -875,8 +863,8 @@ func (suite *BackupIntgSuite) TestContactSerializationRegression() {
|
|||||||
// to be able to successfully query, download and restore event objects
|
// to be able to successfully query, download and restore event objects
|
||||||
func (suite *BackupIntgSuite) TestEventsSerializationRegression() {
|
func (suite *BackupIntgSuite) TestEventsSerializationRegression() {
|
||||||
var (
|
var (
|
||||||
users = []string{suite.user}
|
users = []string{suite.m365.User.ID}
|
||||||
handlers = BackupHandlers(suite.ac)
|
handlers = BackupHandlers(suite.m365.AC)
|
||||||
)
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -911,14 +899,14 @@ func (suite *BackupIntgSuite) TestEventsSerializationRegression() {
|
|||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: inMock.NewProvider(suite.user, suite.user),
|
ProtectedResource: suite.m365.User.Provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
collections, err := CreateCollections(
|
collections, err := CreateCollections(
|
||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
handlers,
|
handlers,
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
test.scope,
|
test.scope,
|
||||||
metadata.DeltaPaths{},
|
metadata.DeltaPaths{},
|
||||||
newStatusUpdater(t, &wg),
|
newStatusUpdater(t, &wg),
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/observe"
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/errs/core"
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
@ -68,21 +69,21 @@ func getItemAndInfo(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
getter itemGetterSerializer,
|
getter itemGetterSerializer,
|
||||||
userID string,
|
userID string,
|
||||||
id string,
|
itemID string,
|
||||||
useImmutableIDs bool,
|
useImmutableIDs bool,
|
||||||
parentPath string,
|
parentPath string,
|
||||||
) ([]byte, *details.ExchangeInfo, error) {
|
) ([]byte, *details.ExchangeInfo, error) {
|
||||||
item, info, err := getter.GetItem(
|
item, info, err := getter.GetItem(
|
||||||
ctx,
|
ctx,
|
||||||
userID,
|
userID,
|
||||||
id,
|
itemID,
|
||||||
fault.New(true)) // temporary way to force a failFast error
|
fault.New(true)) // temporary way to force a failFast error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, clues.WrapWC(ctx, err, "fetching item").
|
return nil, nil, clues.WrapWC(ctx, err, "fetching item").
|
||||||
Label(fault.LabelForceNoBackupCreation)
|
Label(fault.LabelForceNoBackupCreation)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemData, err := getter.Serialize(ctx, item, userID, id)
|
itemData, err := getter.Serialize(ctx, item, userID, itemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, clues.WrapWC(ctx, err, "serializing item")
|
return nil, nil, clues.WrapWC(ctx, err, "serializing item")
|
||||||
}
|
}
|
||||||
@ -108,6 +109,7 @@ func NewCollection(
|
|||||||
bc data.BaseCollection,
|
bc data.BaseCollection,
|
||||||
user string,
|
user string,
|
||||||
items itemGetterSerializer,
|
items itemGetterSerializer,
|
||||||
|
canSkipFailChecker canSkipItemFailurer,
|
||||||
origAdded map[string]time.Time,
|
origAdded map[string]time.Time,
|
||||||
origRemoved []string,
|
origRemoved []string,
|
||||||
validModTimes bool,
|
validModTimes bool,
|
||||||
@ -140,6 +142,7 @@ func NewCollection(
|
|||||||
added: added,
|
added: added,
|
||||||
removed: removed,
|
removed: removed,
|
||||||
getter: items,
|
getter: items,
|
||||||
|
skipChecker: canSkipFailChecker,
|
||||||
statusUpdater: statusUpdater,
|
statusUpdater: statusUpdater,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,6 +153,7 @@ func NewCollection(
|
|||||||
added: added,
|
added: added,
|
||||||
removed: removed,
|
removed: removed,
|
||||||
getter: items,
|
getter: items,
|
||||||
|
skipChecker: canSkipFailChecker,
|
||||||
statusUpdater: statusUpdater,
|
statusUpdater: statusUpdater,
|
||||||
counter: counter,
|
counter: counter,
|
||||||
}
|
}
|
||||||
@ -168,6 +172,7 @@ type prefetchCollection struct {
|
|||||||
removed map[string]struct{}
|
removed map[string]struct{}
|
||||||
|
|
||||||
getter itemGetterSerializer
|
getter itemGetterSerializer
|
||||||
|
skipChecker canSkipItemFailurer
|
||||||
|
|
||||||
statusUpdater support.StatusUpdater
|
statusUpdater support.StatusUpdater
|
||||||
}
|
}
|
||||||
@ -194,11 +199,12 @@ func (col *prefetchCollection) streamItems(
|
|||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
progressMessage chan<- struct{}
|
progressMessage chan<- struct{}
|
||||||
user = col.user
|
user = col.user
|
||||||
|
dataCategory = col.Category().String()
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx = clues.Add(
|
ctx = clues.Add(
|
||||||
ctx,
|
ctx,
|
||||||
"category", col.Category().String())
|
"category", dataCategory)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
close(stream)
|
close(stream)
|
||||||
@ -227,7 +233,7 @@ func (col *prefetchCollection) streamItems(
|
|||||||
defer close(semaphoreCh)
|
defer close(semaphoreCh)
|
||||||
|
|
||||||
// delete all removed items
|
// delete all removed items
|
||||||
for id := range col.removed {
|
for itemID := range col.removed {
|
||||||
semaphoreCh <- struct{}{}
|
semaphoreCh <- struct{}{}
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@ -247,7 +253,7 @@ func (col *prefetchCollection) streamItems(
|
|||||||
if progressMessage != nil {
|
if progressMessage != nil {
|
||||||
progressMessage <- struct{}{}
|
progressMessage <- struct{}{}
|
||||||
}
|
}
|
||||||
}(id)
|
}(itemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -256,7 +262,7 @@ func (col *prefetchCollection) streamItems(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// add any new items
|
// add any new items
|
||||||
for id := range col.added {
|
for itemID := range col.added {
|
||||||
if el.Failure() != nil {
|
if el.Failure() != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -277,8 +283,23 @@ func (col *prefetchCollection) streamItems(
|
|||||||
col.Opts().ToggleFeatures.ExchangeImmutableIDs,
|
col.Opts().ToggleFeatures.ExchangeImmutableIDs,
|
||||||
parentPath)
|
parentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// pulled outside the switch due to multiple return values.
|
||||||
|
cause, canSkip := col.skipChecker.CanSkipItemFailure(
|
||||||
|
err,
|
||||||
|
user,
|
||||||
|
col.Opts())
|
||||||
|
|
||||||
// Handle known error cases
|
// Handle known error cases
|
||||||
switch {
|
switch {
|
||||||
|
case canSkip:
|
||||||
|
// this is a special case handler that allows the item to be skipped
|
||||||
|
// instead of producing an error.
|
||||||
|
errs.AddSkip(ctx, fault.FileSkip(
|
||||||
|
cause,
|
||||||
|
dataCategory,
|
||||||
|
id,
|
||||||
|
id,
|
||||||
|
nil))
|
||||||
case errors.Is(err, core.ErrNotFound):
|
case errors.Is(err, core.ErrNotFound):
|
||||||
// Don't report errors for deleted items as there's no way for us to
|
// Don't report errors for deleted items as there's no way for us to
|
||||||
// back up data that is gone. Record it as a "success", since there's
|
// back up data that is gone. Record it as a "success", since there's
|
||||||
@ -300,6 +321,19 @@ func (col *prefetchCollection) streamItems(
|
|||||||
id,
|
id,
|
||||||
map[string]any{"parentPath": parentPath}))
|
map[string]any{"parentPath": parentPath}))
|
||||||
atomic.AddInt64(&success, 1)
|
atomic.AddInt64(&success, 1)
|
||||||
|
case graph.IsErrCorruptData(err):
|
||||||
|
// These items cannot be downloaded, graph error indicates that the item
|
||||||
|
// data is corrupted. Add to skipped list.
|
||||||
|
logger.
|
||||||
|
CtxErr(ctx, err).
|
||||||
|
With("skipped_reason", fault.SkipCorruptData).
|
||||||
|
Info("inaccessible email")
|
||||||
|
errs.AddSkip(ctx, fault.EmailSkip(
|
||||||
|
fault.SkipCorruptData,
|
||||||
|
user,
|
||||||
|
id,
|
||||||
|
map[string]any{"parentPath": parentPath}))
|
||||||
|
atomic.AddInt64(&success, 1)
|
||||||
default:
|
default:
|
||||||
col.Counter.Inc(count.StreamItemsErred)
|
col.Counter.Inc(count.StreamItemsErred)
|
||||||
el.AddRecoverable(ctx, clues.Wrap(err, "fetching item").Label(fault.LabelForceNoBackupCreation))
|
el.AddRecoverable(ctx, clues.Wrap(err, "fetching item").Label(fault.LabelForceNoBackupCreation))
|
||||||
@ -336,7 +370,7 @@ func (col *prefetchCollection) streamItems(
|
|||||||
if progressMessage != nil {
|
if progressMessage != nil {
|
||||||
progressMessage <- struct{}{}
|
progressMessage <- struct{}{}
|
||||||
}
|
}
|
||||||
}(id)
|
}(itemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@ -365,6 +399,7 @@ type lazyFetchCollection struct {
|
|||||||
removed map[string]struct{}
|
removed map[string]struct{}
|
||||||
|
|
||||||
getter itemGetterSerializer
|
getter itemGetterSerializer
|
||||||
|
skipChecker canSkipItemFailurer
|
||||||
|
|
||||||
statusUpdater support.StatusUpdater
|
statusUpdater support.StatusUpdater
|
||||||
|
|
||||||
@ -391,8 +426,8 @@ func (col *lazyFetchCollection) streamItems(
|
|||||||
var (
|
var (
|
||||||
success int64
|
success int64
|
||||||
progressMessage chan<- struct{}
|
progressMessage chan<- struct{}
|
||||||
|
|
||||||
user = col.user
|
user = col.user
|
||||||
|
el = errs.Local()
|
||||||
)
|
)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -404,7 +439,7 @@ func (col *lazyFetchCollection) streamItems(
|
|||||||
int(success),
|
int(success),
|
||||||
0,
|
0,
|
||||||
col.FullPath().Folder(false),
|
col.FullPath().Folder(false),
|
||||||
errs.Failure())
|
el.Failure())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if len(col.added)+len(col.removed) > 0 {
|
if len(col.added)+len(col.removed) > 0 {
|
||||||
@ -430,7 +465,7 @@ func (col *lazyFetchCollection) streamItems(
|
|||||||
|
|
||||||
// add any new items
|
// add any new items
|
||||||
for id, modTime := range col.added {
|
for id, modTime := range col.added {
|
||||||
if errs.Failure() != nil {
|
if el.Failure() != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,15 +481,18 @@ func (col *lazyFetchCollection) streamItems(
|
|||||||
&lazyItemGetter{
|
&lazyItemGetter{
|
||||||
userID: user,
|
userID: user,
|
||||||
itemID: id,
|
itemID: id,
|
||||||
|
category: col.Category(),
|
||||||
getter: col.getter,
|
getter: col.getter,
|
||||||
modTime: modTime,
|
modTime: modTime,
|
||||||
immutableIDs: col.Opts().ToggleFeatures.ExchangeImmutableIDs,
|
immutableIDs: col.Opts().ToggleFeatures.ExchangeImmutableIDs,
|
||||||
parentPath: parentPath,
|
parentPath: parentPath,
|
||||||
|
skipChecker: col.skipChecker,
|
||||||
|
opts: col.Opts(),
|
||||||
},
|
},
|
||||||
id,
|
id,
|
||||||
modTime,
|
modTime,
|
||||||
col.counter,
|
col.counter,
|
||||||
errs)
|
el)
|
||||||
|
|
||||||
atomic.AddInt64(&success, 1)
|
atomic.AddInt64(&success, 1)
|
||||||
|
|
||||||
@ -468,9 +506,12 @@ type lazyItemGetter struct {
|
|||||||
getter itemGetterSerializer
|
getter itemGetterSerializer
|
||||||
userID string
|
userID string
|
||||||
itemID string
|
itemID string
|
||||||
|
category path.CategoryType
|
||||||
parentPath string
|
parentPath string
|
||||||
modTime time.Time
|
modTime time.Time
|
||||||
immutableIDs bool
|
immutableIDs bool
|
||||||
|
skipChecker canSkipItemFailurer
|
||||||
|
opts control.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lig *lazyItemGetter) GetData(
|
func (lig *lazyItemGetter) GetData(
|
||||||
@ -485,6 +526,25 @@ func (lig *lazyItemGetter) GetData(
|
|||||||
lig.immutableIDs,
|
lig.immutableIDs,
|
||||||
lig.parentPath)
|
lig.parentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if lig.skipChecker != nil {
|
||||||
|
cause, canSkip := lig.skipChecker.CanSkipItemFailure(
|
||||||
|
err,
|
||||||
|
lig.userID,
|
||||||
|
lig.opts)
|
||||||
|
if canSkip {
|
||||||
|
errs.AddSkip(ctx, fault.FileSkip(
|
||||||
|
cause,
|
||||||
|
lig.category.String(),
|
||||||
|
lig.itemID,
|
||||||
|
lig.itemID,
|
||||||
|
nil))
|
||||||
|
|
||||||
|
return nil, nil, false, clues.
|
||||||
|
NewWC(ctx, "error marked as skippable by handler").
|
||||||
|
Label(graph.LabelsSkippable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If an item was deleted then return an empty file so we don't fail
|
// If an item was deleted then return an empty file so we don't fail
|
||||||
// the backup and return a sentinel error when asked for ItemInfo so
|
// the backup and return a sentinel error when asked for ItemInfo so
|
||||||
// we don't display the item in the backup.
|
// we don't display the item in the backup.
|
||||||
@ -499,7 +559,7 @@ func (lig *lazyItemGetter) GetData(
|
|||||||
err = clues.Stack(err)
|
err = clues.Stack(err)
|
||||||
errs.AddRecoverable(ctx, err)
|
errs.AddRecoverable(ctx, err)
|
||||||
|
|
||||||
return nil, nil, false, err
|
return nil, nil, false, clues.Stack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the mod time to what we already told kopia about. This is required
|
// Update the mod time to what we already told kopia about. This is required
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/errs/core"
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
graphTD "github.com/alcionai/corso/src/pkg/services/m365/api/graph/testdata"
|
||||||
)
|
)
|
||||||
@ -153,6 +154,7 @@ func (suite *CollectionUnitSuite) TestNewCollection_state() {
|
|||||||
count.New()),
|
count.New()),
|
||||||
"u",
|
"u",
|
||||||
mock.DefaultItemGetSerialize(),
|
mock.DefaultItemGetSerialize(),
|
||||||
|
mock.NeverCanSkipFailChecker(),
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
colType.validModTimes,
|
colType.validModTimes,
|
||||||
@ -298,6 +300,7 @@ func (suite *CollectionUnitSuite) TestPrefetchCollection_Items() {
|
|||||||
count.New()),
|
count.New()),
|
||||||
"",
|
"",
|
||||||
&mock.ItemGetSerialize{},
|
&mock.ItemGetSerialize{},
|
||||||
|
mock.NeverCanSkipFailChecker(),
|
||||||
test.added,
|
test.added,
|
||||||
maps.Keys(test.removed),
|
maps.Keys(test.removed),
|
||||||
false,
|
false,
|
||||||
@ -333,6 +336,232 @@ func (suite *CollectionUnitSuite) TestPrefetchCollection_Items() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *CollectionUnitSuite) TestPrefetchCollection_Items_skipFailure() {
|
||||||
|
var (
|
||||||
|
start = time.Now().Add(-time.Second)
|
||||||
|
statusUpdater = func(*support.ControllerOperationStatus) {}
|
||||||
|
)
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
category path.CategoryType
|
||||||
|
handler backupHandler
|
||||||
|
added map[string]time.Time
|
||||||
|
removed map[string]struct{}
|
||||||
|
expectItemCount int
|
||||||
|
expectSkippedCount int
|
||||||
|
expectErr assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no items",
|
||||||
|
category: path.EventsCategory,
|
||||||
|
handler: newEventBackupHandler(api.Client{}),
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events only added items",
|
||||||
|
category: path.EventsCategory,
|
||||||
|
handler: newEventBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"fisher": {},
|
||||||
|
"flannigan": {},
|
||||||
|
"fitzbog": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 0,
|
||||||
|
expectSkippedCount: 3,
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events only removed items",
|
||||||
|
category: path.EventsCategory,
|
||||||
|
handler: newEventBackupHandler(api.Client{}),
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"princess": {},
|
||||||
|
"poppy": {},
|
||||||
|
"petunia": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events added and removed items",
|
||||||
|
category: path.EventsCategory,
|
||||||
|
handler: newEventBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"general": {},
|
||||||
|
},
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"general": {},
|
||||||
|
"goose": {},
|
||||||
|
"grumbles": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
// not 1, because general is removed from the added
|
||||||
|
// map due to being in the removed map
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contacts only added items",
|
||||||
|
category: path.ContactsCategory,
|
||||||
|
handler: newContactBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"fisher": {},
|
||||||
|
"flannigan": {},
|
||||||
|
"fitzbog": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 0,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: assert.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contacts only removed items",
|
||||||
|
category: path.ContactsCategory,
|
||||||
|
handler: newContactBackupHandler(api.Client{}),
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"princess": {},
|
||||||
|
"poppy": {},
|
||||||
|
"petunia": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contacts added and removed items",
|
||||||
|
category: path.ContactsCategory,
|
||||||
|
handler: newContactBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"general": {},
|
||||||
|
},
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"general": {},
|
||||||
|
"goose": {},
|
||||||
|
"grumbles": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
// not 1, because general is removed from the added
|
||||||
|
// map due to being in the removed map
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mail only added items",
|
||||||
|
category: path.EmailCategory,
|
||||||
|
handler: newMailBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"fisher": {},
|
||||||
|
"flannigan": {},
|
||||||
|
"fitzbog": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 0,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: assert.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mail only removed items",
|
||||||
|
category: path.EmailCategory,
|
||||||
|
handler: newMailBackupHandler(api.Client{}),
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"princess": {},
|
||||||
|
"poppy": {},
|
||||||
|
"petunia": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mail added and removed items",
|
||||||
|
category: path.EmailCategory,
|
||||||
|
handler: newMailBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"general": {},
|
||||||
|
},
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"general": {},
|
||||||
|
"goose": {},
|
||||||
|
"grumbles": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
// not 1, because general is removed from the added
|
||||||
|
// map due to being in the removed map
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
var (
|
||||||
|
t = suite.T()
|
||||||
|
errs = fault.New(true)
|
||||||
|
itemCount int
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
fullPath, err := path.Build("t", "pr", path.ExchangeService, test.category, false, "fnords", "smarf")
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
locPath, err := path.Build("t", "pr", path.ExchangeService, test.category, false, "fnords", "smarf")
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
opts := control.DefaultOptions()
|
||||||
|
opts.SkipEventsOnInstance503ForResources = map[string]struct{}{}
|
||||||
|
opts.SkipEventsOnInstance503ForResources["pr"] = struct{}{}
|
||||||
|
|
||||||
|
col := NewCollection(
|
||||||
|
data.NewBaseCollection(
|
||||||
|
fullPath,
|
||||||
|
nil,
|
||||||
|
locPath.ToBuilder(),
|
||||||
|
opts,
|
||||||
|
false,
|
||||||
|
count.New()),
|
||||||
|
"pr",
|
||||||
|
&mock.ItemGetSerialize{
|
||||||
|
SerializeErr: graph.ErrServiceUnavailableEmptyResp,
|
||||||
|
},
|
||||||
|
test.handler,
|
||||||
|
test.added,
|
||||||
|
maps.Keys(test.removed),
|
||||||
|
false,
|
||||||
|
statusUpdater,
|
||||||
|
count.New())
|
||||||
|
|
||||||
|
for item := range col.Items(ctx, errs) {
|
||||||
|
itemCount++
|
||||||
|
|
||||||
|
_, rok := test.removed[item.ID()]
|
||||||
|
if rok {
|
||||||
|
dimt, ok := item.(data.ItemModTime)
|
||||||
|
require.True(t, ok, "item implements data.ItemModTime")
|
||||||
|
assert.True(t, dimt.ModTime().After(start), "deleted items should set mod time to now()")
|
||||||
|
assert.True(t, item.Deleted(), "removals should be marked as deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, aok := test.added[item.ID()]
|
||||||
|
if !rok && aok {
|
||||||
|
assert.False(t, item.Deleted(), "additions should not be marked as deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, aok || rok, "item must be either added or removed: %q", item.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
test.expectErr(t, errs.Failure())
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
test.expectItemCount,
|
||||||
|
itemCount,
|
||||||
|
"should see all expected items")
|
||||||
|
assert.Len(t, errs.Skipped(), test.expectSkippedCount)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This test verifies skipped error cases are handled correctly by collection enumeration
|
// This test verifies skipped error cases are handled correctly by collection enumeration
|
||||||
func (suite *CollectionUnitSuite) TestCollection_SkippedErrors() {
|
func (suite *CollectionUnitSuite) TestCollection_SkippedErrors() {
|
||||||
var (
|
var (
|
||||||
@ -364,6 +593,17 @@ func (suite *CollectionUnitSuite) TestCollection_SkippedErrors() {
|
|||||||
},
|
},
|
||||||
expectedSkipError: fault.EmailSkip(fault.SkipInvalidRecipients, "", "fisher", nil),
|
expectedSkipError: fault.EmailSkip(fault.SkipInvalidRecipients, "", "fisher", nil),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ErrorCorruptData",
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"fisher": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 0,
|
||||||
|
itemGetter: &mock.ItemGetSerialize{
|
||||||
|
GetErr: graphTD.ODataErr(string(graph.ErrorCorruptData)),
|
||||||
|
},
|
||||||
|
expectedSkipError: fault.EmailSkip(fault.SkipCorruptData, "", "fisher", nil),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
@ -387,6 +627,7 @@ func (suite *CollectionUnitSuite) TestCollection_SkippedErrors() {
|
|||||||
count.New()),
|
count.New()),
|
||||||
"",
|
"",
|
||||||
test.itemGetter,
|
test.itemGetter,
|
||||||
|
mock.NeverCanSkipFailChecker(),
|
||||||
test.added,
|
test.added,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
@ -467,6 +708,7 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
|
|||||||
expectItemCount: 3,
|
expectItemCount: 3,
|
||||||
expectReads: []string{
|
expectReads: []string{
|
||||||
"fisher",
|
"fisher",
|
||||||
|
"flannigan",
|
||||||
"fitzbog",
|
"fitzbog",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -519,6 +761,7 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
|
|||||||
count.New()),
|
count.New()),
|
||||||
"",
|
"",
|
||||||
mlg,
|
mlg,
|
||||||
|
mock.NeverCanSkipFailChecker(),
|
||||||
test.added,
|
test.added,
|
||||||
maps.Keys(test.removed),
|
maps.Keys(test.removed),
|
||||||
true,
|
true,
|
||||||
@ -530,10 +773,10 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
|
|||||||
|
|
||||||
_, rok := test.removed[item.ID()]
|
_, rok := test.removed[item.ID()]
|
||||||
if rok {
|
if rok {
|
||||||
assert.True(t, item.Deleted(), "removals should be marked as deleted")
|
|
||||||
dimt, ok := item.(data.ItemModTime)
|
dimt, ok := item.(data.ItemModTime)
|
||||||
require.True(t, ok, "item implements data.ItemModTime")
|
require.True(t, ok, "item implements data.ItemModTime")
|
||||||
assert.True(t, dimt.ModTime().After(start), "deleted items should set mod time to now()")
|
assert.True(t, dimt.ModTime().After(start), "deleted items should set mod time to now()")
|
||||||
|
assert.True(t, item.Deleted(), "removals should be marked as deleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
modTime, aok := test.added[item.ID()]
|
modTime, aok := test.added[item.ID()]
|
||||||
@ -542,7 +785,6 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
|
|||||||
// initializer.
|
// initializer.
|
||||||
assert.Implements(t, (*data.ItemModTime)(nil), item)
|
assert.Implements(t, (*data.ItemModTime)(nil), item)
|
||||||
assert.Equal(t, modTime, item.(data.ItemModTime).ModTime(), "item mod time")
|
assert.Equal(t, modTime, item.(data.ItemModTime).ModTime(), "item mod time")
|
||||||
|
|
||||||
assert.False(t, item.Deleted(), "additions should not be marked as deleted")
|
assert.False(t, item.Deleted(), "additions should not be marked as deleted")
|
||||||
|
|
||||||
// Check if the test want's us to read the item's data so the lazy
|
// Check if the test want's us to read the item's data so the lazy
|
||||||
@ -562,6 +804,8 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
|
|||||||
// collection initializer.
|
// collection initializer.
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
assert.Equal(t, modTime, info.Modified(), "ItemInfo mod time")
|
assert.Equal(t, modTime, info.Modified(), "ItemInfo mod time")
|
||||||
|
} else {
|
||||||
|
assert.Fail(t, "unexpected read on item %s", item.ID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,6 +822,294 @@ func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_LazyFetch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *CollectionUnitSuite) TestLazyFetchCollection_Items_skipFailure() {
|
||||||
|
var (
|
||||||
|
start = time.Now().Add(-time.Second)
|
||||||
|
statusUpdater = func(*support.ControllerOperationStatus) {}
|
||||||
|
expectSkip = func(t *testing.T, err error) {
|
||||||
|
assert.Error(t, err, clues.ToCore(err))
|
||||||
|
assert.ErrorContains(t, err, "skip")
|
||||||
|
assert.True(t, clues.HasLabel(err, graph.LabelsSkippable), clues.ToCore(err))
|
||||||
|
}
|
||||||
|
expectNotSkipped = func(t *testing.T, err error) {
|
||||||
|
assert.Error(t, err, clues.ToCore(err))
|
||||||
|
assert.NotContains(t, err.Error(), "skip")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
added map[string]time.Time
|
||||||
|
removed map[string]struct{}
|
||||||
|
category path.CategoryType
|
||||||
|
handler backupHandler
|
||||||
|
expectItemCount int
|
||||||
|
expectSkippedCount int
|
||||||
|
expectReads []string
|
||||||
|
expectErr func(t *testing.T, err error)
|
||||||
|
expectFailure assert.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no items",
|
||||||
|
category: path.EventsCategory,
|
||||||
|
handler: newEventBackupHandler(api.Client{}),
|
||||||
|
expectFailure: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events only added items",
|
||||||
|
category: path.EventsCategory,
|
||||||
|
handler: newEventBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"fisher": start.Add(time.Minute),
|
||||||
|
"flannigan": start.Add(2 * time.Minute),
|
||||||
|
"fitzbog": start.Add(3 * time.Minute),
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 3,
|
||||||
|
expectReads: []string{
|
||||||
|
"fisher",
|
||||||
|
"flannigan",
|
||||||
|
"fitzbog",
|
||||||
|
},
|
||||||
|
expectErr: expectSkip,
|
||||||
|
expectFailure: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events only removed items",
|
||||||
|
category: path.EventsCategory,
|
||||||
|
handler: newEventBackupHandler(api.Client{}),
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"princess": {},
|
||||||
|
"poppy": {},
|
||||||
|
"petunia": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: expectSkip,
|
||||||
|
expectFailure: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events added and removed items",
|
||||||
|
category: path.EventsCategory,
|
||||||
|
handler: newEventBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"general": {},
|
||||||
|
},
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"general": {},
|
||||||
|
"goose": {},
|
||||||
|
"grumbles": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
// not 1, because general is removed from the added
|
||||||
|
// map due to being in the removed map
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: expectSkip,
|
||||||
|
expectFailure: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contacts only added items",
|
||||||
|
category: path.ContactsCategory,
|
||||||
|
handler: newContactBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"fisher": start.Add(time.Minute),
|
||||||
|
"flannigan": start.Add(2 * time.Minute),
|
||||||
|
"fitzbog": start.Add(3 * time.Minute),
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectReads: []string{
|
||||||
|
"fisher",
|
||||||
|
"flannigan",
|
||||||
|
"fitzbog",
|
||||||
|
},
|
||||||
|
expectErr: expectNotSkipped,
|
||||||
|
expectFailure: assert.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contacts only removed items",
|
||||||
|
category: path.ContactsCategory,
|
||||||
|
handler: newContactBackupHandler(api.Client{}),
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"princess": {},
|
||||||
|
"poppy": {},
|
||||||
|
"petunia": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: expectNotSkipped,
|
||||||
|
expectFailure: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contacts added and removed items",
|
||||||
|
category: path.ContactsCategory,
|
||||||
|
handler: newContactBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"general": {},
|
||||||
|
},
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"general": {},
|
||||||
|
"goose": {},
|
||||||
|
"grumbles": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
// not 1, because general is removed from the added
|
||||||
|
// map due to being in the removed map
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: expectNotSkipped,
|
||||||
|
expectFailure: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mail only added items",
|
||||||
|
category: path.EmailCategory,
|
||||||
|
handler: newMailBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"fisher": start.Add(time.Minute),
|
||||||
|
"flannigan": start.Add(2 * time.Minute),
|
||||||
|
"fitzbog": start.Add(3 * time.Minute),
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectReads: []string{
|
||||||
|
"fisher",
|
||||||
|
"flannigan",
|
||||||
|
"fitzbog",
|
||||||
|
},
|
||||||
|
expectErr: expectNotSkipped,
|
||||||
|
expectFailure: assert.Error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mail only removed items",
|
||||||
|
category: path.EmailCategory,
|
||||||
|
handler: newMailBackupHandler(api.Client{}),
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"princess": {},
|
||||||
|
"poppy": {},
|
||||||
|
"petunia": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: expectNotSkipped,
|
||||||
|
expectFailure: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mail added and removed items",
|
||||||
|
category: path.EmailCategory,
|
||||||
|
handler: newMailBackupHandler(api.Client{}),
|
||||||
|
added: map[string]time.Time{
|
||||||
|
"general": {},
|
||||||
|
},
|
||||||
|
removed: map[string]struct{}{
|
||||||
|
"general": {},
|
||||||
|
"goose": {},
|
||||||
|
"grumbles": {},
|
||||||
|
},
|
||||||
|
expectItemCount: 3,
|
||||||
|
// not 1, because general is removed from the added
|
||||||
|
// map due to being in the removed map
|
||||||
|
expectSkippedCount: 0,
|
||||||
|
expectErr: expectNotSkipped,
|
||||||
|
expectFailure: assert.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
var (
|
||||||
|
t = suite.T()
|
||||||
|
errs = fault.New(false)
|
||||||
|
itemCount int
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
fullPath, err := path.Build("t", "pr", path.ExchangeService, test.category, false, "fnords", "smarf")
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
locPath, err := path.Build("t", "pr", path.ExchangeService, test.category, false, "fnords", "smarf")
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
mlg := &mockLazyItemGetterSerializer{
|
||||||
|
ItemGetSerialize: &mock.ItemGetSerialize{
|
||||||
|
SerializeErr: graph.ErrServiceUnavailableEmptyResp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer mlg.check(t, test.expectReads)
|
||||||
|
|
||||||
|
opts := control.DefaultOptions()
|
||||||
|
opts.SkipEventsOnInstance503ForResources = map[string]struct{}{}
|
||||||
|
opts.SkipEventsOnInstance503ForResources["pr"] = struct{}{}
|
||||||
|
|
||||||
|
col := NewCollection(
|
||||||
|
data.NewBaseCollection(
|
||||||
|
fullPath,
|
||||||
|
nil,
|
||||||
|
locPath.ToBuilder(),
|
||||||
|
opts,
|
||||||
|
false,
|
||||||
|
count.New()),
|
||||||
|
"pr",
|
||||||
|
mlg,
|
||||||
|
test.handler,
|
||||||
|
test.added,
|
||||||
|
maps.Keys(test.removed),
|
||||||
|
true,
|
||||||
|
statusUpdater,
|
||||||
|
count.New())
|
||||||
|
|
||||||
|
for item := range col.Items(ctx, errs) {
|
||||||
|
itemCount++
|
||||||
|
|
||||||
|
_, rok := test.removed[item.ID()]
|
||||||
|
if rok {
|
||||||
|
dimt, ok := item.(data.ItemModTime)
|
||||||
|
require.True(t, ok, "item implements data.ItemModTime")
|
||||||
|
assert.True(t, dimt.ModTime().After(start), "deleted items should set mod time to now()")
|
||||||
|
assert.True(t, item.Deleted(), "removals should be marked as deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
modTime, aok := test.added[item.ID()]
|
||||||
|
if !rok && aok {
|
||||||
|
// Item's mod time should be what's passed into the collection
|
||||||
|
// initializer.
|
||||||
|
assert.Implements(t, (*data.ItemModTime)(nil), item)
|
||||||
|
assert.Equal(t, modTime, item.(data.ItemModTime).ModTime(), "item mod time")
|
||||||
|
assert.False(t, item.Deleted(), "additions should not be marked as deleted")
|
||||||
|
|
||||||
|
// Check if the test want's us to read the item's data so the lazy
|
||||||
|
// data fetch is executed.
|
||||||
|
if slices.Contains(test.expectReads, item.ID()) {
|
||||||
|
r := item.ToReader()
|
||||||
|
|
||||||
|
_, err := io.ReadAll(r)
|
||||||
|
test.expectErr(t, err)
|
||||||
|
|
||||||
|
r.Close()
|
||||||
|
} else {
|
||||||
|
assert.Fail(t, "unexpected read on item %s", item.ID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, aok || rok, "item must be either added or removed: %q", item.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
failure := errs.Failure()
|
||||||
|
if failure == nil && len(errs.Recovered()) > 0 {
|
||||||
|
failure = errs.Recovered()[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
test.expectFailure(t, failure, clues.ToCore(failure))
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
test.expectItemCount,
|
||||||
|
itemCount,
|
||||||
|
"should see all expected items")
|
||||||
|
assert.Len(t, errs.Skipped(), test.expectSkippedCount)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *CollectionUnitSuite) TestLazyItem_NoRead_GetInfo_Errors() {
|
func (suite *CollectionUnitSuite) TestLazyItem_NoRead_GetInfo_Errors() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package exchange
|
package exchange
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
)
|
)
|
||||||
@ -52,3 +54,11 @@ func (h contactBackupHandler) NewContainerCache(
|
|||||||
getter: h.ac,
|
getter: h.ac,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h contactBackupHandler) CanSkipItemFailure(
|
||||||
|
err error,
|
||||||
|
resourceID string,
|
||||||
|
opts control.Options,
|
||||||
|
) (fault.SkipCause, bool) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,83 @@
|
|||||||
|
package exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContactsBackupHandlerUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContactsBackupHandlerUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &ContactsBackupHandlerUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ContactsBackupHandlerUnitSuite) TestHandler_CanSkipItemFailure() {
|
||||||
|
resourceID := uuid.NewString()
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
opts control.Options
|
||||||
|
expect assert.BoolAssertionFunc
|
||||||
|
expectCause fault.SkipCause
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no config",
|
||||||
|
err: assert.AnError,
|
||||||
|
opts: control.Options{},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false when map is empty",
|
||||||
|
err: assert.AnError,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false on nil error",
|
||||||
|
err: nil,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
resourceID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false even if resource matches",
|
||||||
|
err: assert.AnError,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
resourceID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
h := newContactBackupHandler(api.Client{})
|
||||||
|
cause, result := h.CanSkipItemFailure(
|
||||||
|
test.err,
|
||||||
|
resourceID,
|
||||||
|
test.opts)
|
||||||
|
|
||||||
|
test.expect(t, result)
|
||||||
|
assert.Equal(t, test.expectCause, cause)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
@ -54,7 +55,7 @@ func (m *contactRestoreMock) DeleteItem(
|
|||||||
|
|
||||||
type ContactsRestoreIntgSuite struct {
|
type ContactsRestoreIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContactsRestoreIntgSuite(t *testing.T) {
|
func TestContactsRestoreIntgSuite(t *testing.T) {
|
||||||
@ -66,17 +67,17 @@ func TestContactsRestoreIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ContactsRestoreIntgSuite) SetupSuite() {
|
func (suite *ContactsRestoreIntgSuite) SetupSuite() {
|
||||||
suite.its = newIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing to ensure that cache system works for in multiple different environments
|
// Testing to ensure that cache system works for in multiple different environments
|
||||||
func (suite *ContactsRestoreIntgSuite) TestCreateContainerDestination() {
|
func (suite *ContactsRestoreIntgSuite) TestCreateContainerDestination() {
|
||||||
runCreateDestinationTest(
|
runCreateDestinationTest(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
newContactRestoreHandler(suite.its.ac),
|
newContactRestoreHandler(suite.m365.AC),
|
||||||
path.ContactsCategory,
|
path.ContactsCategory,
|
||||||
suite.its.creds.AzureTenantID,
|
suite.m365.TenantID,
|
||||||
suite.its.userID,
|
suite.m365.User.ID,
|
||||||
testdata.DefaultRestoreConfig("").Location,
|
testdata.DefaultRestoreConfig("").Location,
|
||||||
[]string{"Hufflepuff"},
|
[]string{"Hufflepuff"},
|
||||||
[]string{"Ravenclaw"})
|
[]string{"Ravenclaw"})
|
||||||
@ -207,17 +208,16 @@ func (suite *ContactsRestoreIntgSuite) TestRestoreContact() {
|
|||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
ctr := count.New()
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
ctr := count.New()
|
|
||||||
|
|
||||||
_, err := restoreContact(
|
_, err := restoreContact(
|
||||||
ctx,
|
ctx,
|
||||||
test.apiMock,
|
test.apiMock,
|
||||||
body,
|
body,
|
||||||
suite.its.userID,
|
suite.m365.User.ID,
|
||||||
"destination",
|
"destination",
|
||||||
test.collisionMap,
|
test.collisionMap,
|
||||||
test.onCollision,
|
test.onCollision,
|
||||||
|
|||||||
@ -3,11 +3,13 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -16,10 +18,8 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
|
||||||
"github.com/alcionai/corso/src/pkg/errs/core"
|
"github.com/alcionai/corso/src/pkg/errs/core"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -1019,49 +1019,241 @@ func (suite *ConfiguredFolderCacheUnitSuite) TestAddToCache() {
|
|||||||
assert.Equal(t, m.expectedLocation, l.String(), "location path")
|
assert.Equal(t, m.expectedLocation, l.String(), "location path")
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerResolverSuite struct {
|
// ---------------------------------------------------------------------------
|
||||||
tester.Suite
|
// EventContainerCache unit tests
|
||||||
credentials account.M365Config
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var _ containerGetter = mockEventContainerGetter{}
|
||||||
|
|
||||||
|
type mockEventContainerGetter struct {
|
||||||
|
// containerGetter returns graph.CalendarDisplayable, unlike containersEnumerator
|
||||||
|
// which returns models.Calendarable.
|
||||||
|
idToCalendar map[string]graph.CalendarDisplayable
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerResolverIntegrationSuite(t *testing.T) {
|
func (m mockEventContainerGetter) GetContainerByID(
|
||||||
suite.Run(t, &ContainerResolverSuite{
|
ctx context.Context,
|
||||||
|
userID string,
|
||||||
|
dirID string,
|
||||||
|
) (graph.Container, error) {
|
||||||
|
return m.idToCalendar[dirID], m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ containersEnumerator[models.Calendarable] = mockEventContainersEnumerator{}
|
||||||
|
|
||||||
|
type mockEventContainersEnumerator struct {
|
||||||
|
containers []models.Calendarable
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockEventContainersEnumerator) EnumerateContainers(
|
||||||
|
ctx context.Context,
|
||||||
|
userID string,
|
||||||
|
baseDirID string,
|
||||||
|
) ([]models.Calendarable, error) {
|
||||||
|
return m.containers, m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventsContainerUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventsContainerUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &EventsContainerUnitSuite{
|
||||||
|
Suite: tester.NewUnitSuite(t),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCalendar(
|
||||||
|
id, name, ownerEmail string,
|
||||||
|
isDefault bool,
|
||||||
|
) *models.Calendar {
|
||||||
|
c := models.NewCalendar()
|
||||||
|
|
||||||
|
c.SetId(ptr.To(id))
|
||||||
|
c.SetName(ptr.To(name))
|
||||||
|
c.SetIsDefaultCalendar(ptr.To(isDefault))
|
||||||
|
|
||||||
|
if len(ownerEmail) > 0 {
|
||||||
|
email := models.NewEmailAddress()
|
||||||
|
|
||||||
|
email.SetAddress(ptr.To(ownerEmail))
|
||||||
|
// Set crc as the name for keeping this func simple.
|
||||||
|
eName := fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(ownerEmail)))
|
||||||
|
email.SetName(ptr.To(eName))
|
||||||
|
c.SetOwner(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if we skip backup of shared calendars. These will be backed up for
|
||||||
|
// the resource owner that owns the calendar.
|
||||||
|
func (suite *EventsContainerUnitSuite) TestPopulate_SkipSharedCalendars() {
|
||||||
|
// map of calendars
|
||||||
|
calendars := map[string]models.Calendarable{
|
||||||
|
// Default calendars Dx
|
||||||
|
"D0": makeCalendar(api.DefaultCalendar, api.DefaultCalendar, "owner@bar.com", true),
|
||||||
|
// Atypical, but creating another default calendar for testing purposes.
|
||||||
|
"D1": makeCalendar("D1", "D1", "owner@bar.com", true),
|
||||||
|
// Shared calendars Sx
|
||||||
|
"S0": makeCalendar("S0", "S0", "sharer@bar.com", false),
|
||||||
|
// Owned calendars, not default Ox
|
||||||
|
"O0": makeCalendar("O0", "O0", "owner@bar.com", false),
|
||||||
|
// Calendars with missing owner informaton
|
||||||
|
"M0": makeCalendar("M0", "M0", "", false),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always return default calendar from the getter.
|
||||||
|
getContainersByID := func() map[string]graph.CalendarDisplayable {
|
||||||
|
return map[string]graph.CalendarDisplayable{
|
||||||
|
api.DefaultCalendar: *graph.CreateCalendarDisplayable(calendars["D0"], "parentID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
enumerateContainers func() []models.Calendarable
|
||||||
|
expectErr assert.ErrorAssertionFunc
|
||||||
|
assertFunc func(t *testing.T, ecc *eventContainerCache)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "one default calendar, one shared",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["D0"],
|
||||||
|
calendars["S0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 1, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2 default calendars, 1 shared",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["D0"],
|
||||||
|
calendars["D1"],
|
||||||
|
calendars["S0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 2, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
assert.NotNil(t, ecc.cache["D1"], "missing default calendar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1 default, 1 additional owned, 1 shared",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["D0"],
|
||||||
|
calendars["O0"],
|
||||||
|
calendars["S0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 2, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
assert.NotNil(t, ecc.cache["O0"], "missing owned calendar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1 default, 1 with missing owner information",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["D0"],
|
||||||
|
calendars["M0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 2, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
assert.NotNil(t, ecc.cache["M0"], "missing calendar with missing owner info")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Unlikely to happen, but we should back up the calendar if the default owner
|
||||||
|
// cannot be determined, i.e. default calendar is missing.
|
||||||
|
name: "default owner info missing",
|
||||||
|
enumerateContainers: func() []models.Calendarable {
|
||||||
|
return []models.Calendarable{
|
||||||
|
calendars["S0"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expectErr: assert.NoError,
|
||||||
|
assertFunc: func(t *testing.T, ecc *eventContainerCache) {
|
||||||
|
assert.Len(t, ecc.cache, 2, "expected calendar count")
|
||||||
|
assert.NotNil(t, ecc.cache[api.DefaultCalendar], "missing default calendar")
|
||||||
|
assert.NotNil(t, ecc.cache["S0"], "missing additional calendar")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
ecc := &eventContainerCache{
|
||||||
|
userID: "test",
|
||||||
|
enumer: mockEventContainersEnumerator{containers: test.enumerateContainers()},
|
||||||
|
getter: mockEventContainerGetter{idToCalendar: getContainersByID()},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ecc.Populate(ctx, fault.New(true), "root", "root")
|
||||||
|
test.expectErr(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
test.assertFunc(t, ecc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// container resolver integration suite
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ContainerResolverIntgSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
m365 its.M365IntgTestSetup
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerResolverIntgSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &ContainerResolverIntgSuite{
|
||||||
Suite: tester.NewIntegrationSuite(
|
Suite: tester.NewIntegrationSuite(
|
||||||
t,
|
t,
|
||||||
[][]string{tconfig.M365AcctCredEnvs}),
|
[][]string{tconfig.M365AcctCredEnvs}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ContainerResolverSuite) SetupSuite() {
|
func (suite *ContainerResolverIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
m365, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.credentials = m365
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ContainerResolverSuite) TestPopulate() {
|
func (suite *ContainerResolverIntgSuite) TestPopulate() {
|
||||||
ac, err := api.NewClient(
|
|
||||||
suite.credentials,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(suite.T(), err, clues.ToCore(err))
|
|
||||||
|
|
||||||
eventFunc := func(t *testing.T) graph.ContainerResolver {
|
eventFunc := func(t *testing.T) graph.ContainerResolver {
|
||||||
return &eventContainerCache{
|
return &eventContainerCache{
|
||||||
userID: tconfig.M365UserID(t),
|
userID: tconfig.M365UserID(t),
|
||||||
enumer: ac.Events(),
|
enumer: suite.m365.AC.Events(),
|
||||||
getter: ac.Events(),
|
getter: suite.m365.AC.Events(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contactFunc := func(t *testing.T) graph.ContainerResolver {
|
contactFunc := func(t *testing.T) graph.ContainerResolver {
|
||||||
return &contactContainerCache{
|
return &contactContainerCache{
|
||||||
userID: tconfig.M365UserID(t),
|
userID: tconfig.M365UserID(t),
|
||||||
enumer: ac.Contacts(),
|
enumer: suite.m365.AC.Contacts(),
|
||||||
getter: ac.Contacts(),
|
getter: suite.m365.AC.Contacts(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
package exchange
|
package exchange
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
)
|
)
|
||||||
@ -52,3 +59,32 @@ func (h eventBackupHandler) NewContainerCache(
|
|||||||
getter: h.ac,
|
getter: h.ac,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: this could be further improved buy specifying the call source and matching that
|
||||||
|
// with the expected error. Might be necessary if we use this for more than one error.
|
||||||
|
// But since we only call this in a single place at this time, that additional guard isn't
|
||||||
|
// built into the func.
|
||||||
|
func (h eventBackupHandler) CanSkipItemFailure(
|
||||||
|
err error,
|
||||||
|
resourceID string,
|
||||||
|
opts control.Options,
|
||||||
|
) (fault.SkipCause, bool) {
|
||||||
|
if err == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is a bit overly cautious. we do know that we get 503s with empty response bodies
|
||||||
|
// due to fauilures when getting too many instances. We don't know for sure if we get
|
||||||
|
// generic, well formed 503s. But since we're working with specific resources and item
|
||||||
|
// IDs in the first place, that extra caution will help make sure an unexpected error dosn't
|
||||||
|
// slip through the cracks on us.
|
||||||
|
if !errors.Is(err, graph.ErrServiceUnavailableEmptyResp) &&
|
||||||
|
!clues.HasLabel(err, graph.LabelStatus(http.StatusServiceUnavailable)) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := opts.SkipEventsOnInstance503ForResources[resourceID]
|
||||||
|
|
||||||
|
// strict equals required here. ids are case sensitive.
|
||||||
|
return fault.SkipKnownEventInstance503s, ok
|
||||||
|
}
|
||||||
|
|||||||
112
src/internal/m365/collection/exchange/events_backup_test.go
Normal file
112
src/internal/m365/collection/exchange/events_backup_test.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventsBackupHandlerUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventsBackupHandlerUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &EventsBackupHandlerUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventsBackupHandlerUnitSuite) TestHandler_CanSkipItemFailure() {
|
||||||
|
resourceID := uuid.NewString()
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
opts control.Options
|
||||||
|
expect assert.BoolAssertionFunc
|
||||||
|
expectCause fault.SkipCause
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no config",
|
||||||
|
err: graph.ErrServiceUnavailableEmptyResp,
|
||||||
|
opts: control.Options{},
|
||||||
|
expect: assert.False,
|
||||||
|
expectCause: fault.SkipKnownEventInstance503s,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty skip on 503",
|
||||||
|
err: graph.ErrServiceUnavailableEmptyResp,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
expectCause: fault.SkipKnownEventInstance503s,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil error",
|
||||||
|
err: nil,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
resourceID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-matching resource",
|
||||||
|
err: graph.ErrServiceUnavailableEmptyResp,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
"foo": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
expectCause: fault.SkipKnownEventInstance503s,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "match on instance 503 empty resp",
|
||||||
|
err: graph.ErrServiceUnavailableEmptyResp,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
resourceID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.True,
|
||||||
|
expectCause: fault.SkipKnownEventInstance503s,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "match on instance 503",
|
||||||
|
err: clues.New("arbitrary error").
|
||||||
|
Label(graph.LabelStatus(http.StatusServiceUnavailable)),
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
resourceID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.True,
|
||||||
|
expectCause: fault.SkipKnownEventInstance503s,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
h := newEventBackupHandler(api.Client{})
|
||||||
|
cause, result := h.CanSkipItemFailure(
|
||||||
|
test.err,
|
||||||
|
resourceID,
|
||||||
|
test.opts)
|
||||||
|
|
||||||
|
test.expect(t, result)
|
||||||
|
assert.Equal(t, test.expectCause, cause)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package exchange
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
@ -60,6 +61,16 @@ func (ecc *eventContainerCache) populateEventRoot(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSharedCalendar(defaultCalendarOwner string, c models.Calendarable) bool {
|
||||||
|
// If we can't determine the owner, assume the calendar is owned by the
|
||||||
|
// user.
|
||||||
|
if len(defaultCalendarOwner) == 0 || c.GetOwner() == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !strings.EqualFold(defaultCalendarOwner, ptr.Val(c.GetOwner().GetAddress()))
|
||||||
|
}
|
||||||
|
|
||||||
// Populate utility function for populating eventCalendarCache.
|
// Populate utility function for populating eventCalendarCache.
|
||||||
// Executes 1 additional Graph Query
|
// Executes 1 additional Graph Query
|
||||||
// @param baseID: ignored. Present to conform to interface
|
// @param baseID: ignored. Present to conform to interface
|
||||||
@ -89,11 +100,39 @@ func (ecc *eventContainerCache) Populate(
|
|||||||
return clues.WrapWC(ctx, err, "enumerating containers")
|
return clues.WrapWC(ctx, err, "enumerating containers")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultCalendarOwner string
|
||||||
|
|
||||||
|
// Determine the owner for the default calendar. We'll use this to detect and
|
||||||
|
// skip shared calendars that are not owned by this user.
|
||||||
|
for _, c := range containers {
|
||||||
|
if ptr.Val(c.GetIsDefaultCalendar()) && c.GetOwner() != nil {
|
||||||
|
defaultCalendarOwner = ptr.Val(c.GetOwner().GetAddress())
|
||||||
|
ctx = clues.Add(ctx, "default_calendar_owner", defaultCalendarOwner)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
if el.Failure() != nil {
|
if el.Failure() != nil {
|
||||||
return el.Failure()
|
return el.Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip shared calendars if we have enough information to determine the owner
|
||||||
|
if isSharedCalendar(defaultCalendarOwner, c) {
|
||||||
|
var ownerEmail string
|
||||||
|
if c.GetOwner() != nil {
|
||||||
|
ownerEmail = ptr.Val(c.GetOwner().GetAddress())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Ctx(ctx).Infow(
|
||||||
|
"skipping shared calendar",
|
||||||
|
"name", ptr.Val(c.GetName()),
|
||||||
|
"owner", ownerEmail)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
cacheFolder := graph.NewCacheFolder(
|
cacheFolder := graph.NewCacheFolder(
|
||||||
api.CalendarDisplayable{Calendarable: c},
|
api.CalendarDisplayable{Calendarable: c},
|
||||||
path.Builder{}.Append(ptr.Val(c.GetId())),
|
path.Builder{}.Append(ptr.Val(c.GetId())),
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
@ -101,7 +102,7 @@ func (m *eventRestoreMock) PatchItem(
|
|||||||
|
|
||||||
type EventsRestoreIntgSuite struct {
|
type EventsRestoreIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEventsRestoreIntgSuite(t *testing.T) {
|
func TestEventsRestoreIntgSuite(t *testing.T) {
|
||||||
@ -113,17 +114,17 @@ func TestEventsRestoreIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *EventsRestoreIntgSuite) SetupSuite() {
|
func (suite *EventsRestoreIntgSuite) SetupSuite() {
|
||||||
suite.its = newIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing to ensure that cache system works for in multiple different environments
|
// Testing to ensure that cache system works for in multiple different environments
|
||||||
func (suite *EventsRestoreIntgSuite) TestCreateContainerDestination() {
|
func (suite *EventsRestoreIntgSuite) TestCreateContainerDestination() {
|
||||||
runCreateDestinationTest(
|
runCreateDestinationTest(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
newEventRestoreHandler(suite.its.ac),
|
newEventRestoreHandler(suite.m365.AC),
|
||||||
path.EventsCategory,
|
path.EventsCategory,
|
||||||
suite.its.creds.AzureTenantID,
|
suite.m365.TenantID,
|
||||||
suite.its.userID,
|
suite.m365.User.ID,
|
||||||
testdata.DefaultRestoreConfig("").Location,
|
testdata.DefaultRestoreConfig("").Location,
|
||||||
[]string{"Durmstrang"},
|
[]string{"Durmstrang"},
|
||||||
[]string{"Beauxbatons"})
|
[]string{"Beauxbatons"})
|
||||||
@ -264,7 +265,7 @@ func (suite *EventsRestoreIntgSuite) TestRestoreEvent() {
|
|||||||
ctx,
|
ctx,
|
||||||
test.apiMock,
|
test.apiMock,
|
||||||
body,
|
body,
|
||||||
suite.its.userID,
|
suite.m365.User.ID,
|
||||||
"destination",
|
"destination",
|
||||||
test.collisionMap,
|
test.collisionMap,
|
||||||
test.onCollision,
|
test.onCollision,
|
||||||
|
|||||||
@ -26,6 +26,8 @@ type backupHandler interface {
|
|||||||
previewIncludeContainers() []string
|
previewIncludeContainers() []string
|
||||||
previewExcludeContainers() []string
|
previewExcludeContainers() []string
|
||||||
NewContainerCache(userID string) (string, graph.ContainerResolver)
|
NewContainerCache(userID string) (string, graph.ContainerResolver)
|
||||||
|
|
||||||
|
canSkipItemFailurer
|
||||||
}
|
}
|
||||||
|
|
||||||
type addedAndRemovedItemGetter interface {
|
type addedAndRemovedItemGetter interface {
|
||||||
@ -57,6 +59,14 @@ func BackupHandlers(ac api.Client) map[path.CategoryType]backupHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type canSkipItemFailurer interface {
|
||||||
|
CanSkipItemFailure(
|
||||||
|
err error,
|
||||||
|
resourceID string,
|
||||||
|
opts control.Options,
|
||||||
|
) (fault.SkipCause, bool)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// restore
|
// restore
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
package exchange
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
type intgTesterSetup struct {
|
|
||||||
ac api.Client
|
|
||||||
creds account.M365Config
|
|
||||||
userID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIntegrationTesterSetup(t *testing.T) intgTesterSetup {
|
|
||||||
its := intgTesterSetup{}
|
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
creds, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
its.creds = creds
|
|
||||||
|
|
||||||
its.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
its.userID = tconfig.GetM365UserID(ctx)
|
|
||||||
|
|
||||||
return its
|
|
||||||
}
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
package exchange
|
package exchange
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
)
|
)
|
||||||
@ -57,3 +59,11 @@ func (h mailBackupHandler) NewContainerCache(
|
|||||||
getter: h.ac,
|
getter: h.ac,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h mailBackupHandler) CanSkipItemFailure(
|
||||||
|
err error,
|
||||||
|
resourceID string,
|
||||||
|
opts control.Options,
|
||||||
|
) (fault.SkipCause, bool) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|||||||
83
src/internal/m365/collection/exchange/mail_backup_test.go
Normal file
83
src/internal/m365/collection/exchange/mail_backup_test.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MailBackupHandlerUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMailBackupHandlerUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &MailBackupHandlerUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *MailBackupHandlerUnitSuite) TestHandler_CanSkipItemFailure() {
|
||||||
|
resourceID := uuid.NewString()
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
opts control.Options
|
||||||
|
expect assert.BoolAssertionFunc
|
||||||
|
expectCause fault.SkipCause
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no config",
|
||||||
|
err: assert.AnError,
|
||||||
|
opts: control.Options{},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false when map is empty",
|
||||||
|
err: assert.AnError,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false on nil error",
|
||||||
|
err: nil,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
resourceID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false even if resource matches",
|
||||||
|
err: assert.AnError,
|
||||||
|
opts: control.Options{
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
resourceID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: assert.False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
h := newMailBackupHandler(api.Client{})
|
||||||
|
cause, result := h.CanSkipItemFailure(
|
||||||
|
test.err,
|
||||||
|
resourceID,
|
||||||
|
test.opts)
|
||||||
|
|
||||||
|
test.expect(t, result)
|
||||||
|
assert.Equal(t, test.expectCause, cause)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,10 +10,8 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
@ -30,30 +28,24 @@ const (
|
|||||||
expectedFolderPath = "toplevel/subFolder/subsubfolder"
|
expectedFolderPath = "toplevel/subFolder/subsubfolder"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MailFolderCacheIntegrationSuite struct {
|
type MailFolderCacheIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
credentials account.M365Config
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMailFolderCacheIntegrationSuite(t *testing.T) {
|
func TestMailFolderCacheIntegrationSuite(t *testing.T) {
|
||||||
suite.Run(t, &MailFolderCacheIntegrationSuite{
|
suite.Run(t, &MailFolderCacheIntgSuite{
|
||||||
Suite: tester.NewIntegrationSuite(
|
Suite: tester.NewIntegrationSuite(
|
||||||
t,
|
t,
|
||||||
[][]string{tconfig.M365AcctCredEnvs}),
|
[][]string{tconfig.M365AcctCredEnvs}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *MailFolderCacheIntegrationSuite) SetupSuite() {
|
func (suite *MailFolderCacheIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
m365, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.credentials = m365
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() {
|
func (suite *MailFolderCacheIntgSuite) TestDeltaFetch() {
|
||||||
suite.T().Skipf("Test depends on hardcoded folder names. Skipping till that is fixed")
|
suite.T().Skipf("Test depends on hardcoded folder names. Skipping till that is fixed")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -75,7 +67,6 @@ func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() {
|
|||||||
path: []string{"some", "leading", "path"},
|
path: []string{"some", "leading", "path"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
userID := tconfig.M365UserID(suite.T())
|
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
@ -84,21 +75,15 @@ func (suite *MailFolderCacheIntegrationSuite) TestDeltaFetch() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
ac, err := api.NewClient(
|
acm := suite.m365.AC.Mail()
|
||||||
suite.credentials,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
acm := ac.Mail()
|
|
||||||
|
|
||||||
mfc := mailContainerCache{
|
mfc := mailContainerCache{
|
||||||
userID: userID,
|
userID: suite.m365.User.ID,
|
||||||
enumer: acm,
|
enumer: acm,
|
||||||
getter: acm,
|
getter: acm,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mfc.Populate(ctx, fault.New(true), test.root, test.path...)
|
err := mfc.Populate(ctx, fault.New(true), test.root, test.path...)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
p, l, err := mfc.IDToPath(ctx, testFolderID)
|
p, l, err := mfc.IDToPath(ctx, testFolderID)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package exchange
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
@ -147,6 +148,8 @@ func restoreMail(
|
|||||||
|
|
||||||
msg = setMessageSVEPs(toMessage(msg))
|
msg = setMessageSVEPs(toMessage(msg))
|
||||||
|
|
||||||
|
setReplyTos(msg)
|
||||||
|
|
||||||
attachments := msg.GetAttachments()
|
attachments := msg.GetAttachments()
|
||||||
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
|
||||||
msg.SetAttachments([]models.Attachmentable{})
|
msg.SetAttachments([]models.Attachmentable{})
|
||||||
@ -229,6 +232,38 @@ func setMessageSVEPs(msg models.Messageable) models.Messageable {
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setReplyTos(msg models.Messageable) {
|
||||||
|
var (
|
||||||
|
replyTos = msg.GetReplyTo()
|
||||||
|
emailAddress models.EmailAddressable
|
||||||
|
name, address string
|
||||||
|
sanitizedReplyTos = make([]models.Recipientable, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(replyTos) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, replyTo := range replyTos {
|
||||||
|
emailAddress = replyTo.GetEmailAddress()
|
||||||
|
address = ptr.Val(emailAddress.GetAddress())
|
||||||
|
name = ptr.Val(emailAddress.GetName())
|
||||||
|
|
||||||
|
if isValidEmail(address) || isValidDN(address) {
|
||||||
|
newEmailAddress := models.NewEmailAddress()
|
||||||
|
newEmailAddress.SetAddress(ptr.To(address))
|
||||||
|
newEmailAddress.SetName(ptr.To(name))
|
||||||
|
|
||||||
|
sanitizedReplyTo := models.NewRecipient()
|
||||||
|
sanitizedReplyTo.SetEmailAddress(newEmailAddress)
|
||||||
|
|
||||||
|
sanitizedReplyTos = append(sanitizedReplyTos, sanitizedReplyTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.SetReplyTo(sanitizedReplyTos)
|
||||||
|
}
|
||||||
|
|
||||||
func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
|
func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, containerID string,
|
userID, containerID string,
|
||||||
@ -240,3 +275,24 @@ func (h mailRestoreHandler) GetItemsInContainerByCollisionKey(
|
|||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [TODO]relocate to a common place
|
||||||
|
func isValidEmail(email string) bool {
|
||||||
|
emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
|
||||||
|
r := regexp.MustCompile(emailRegex)
|
||||||
|
|
||||||
|
return r.MatchString(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidDN check if given string's format matches that of a MSFT Distinguished Name
|
||||||
|
// This regular expression matches strings that start with /o=,
|
||||||
|
// followed by any characters except /,
|
||||||
|
// then /ou=, followed by any characters except /,
|
||||||
|
// then /cn=, followed by any characters except /,
|
||||||
|
// then /cn= followed by a 32-character hexadecimal string followed by - and any additional characters.
|
||||||
|
func isValidDN(dn string) bool {
|
||||||
|
dnRegex := `^/o=[^/]+/ou=[^/]+/cn=[^/]+/cn=[a-fA-F0-9]{32}-[a-zA-Z0-9-]+$`
|
||||||
|
r := regexp.MustCompile(dnRegex)
|
||||||
|
|
||||||
|
return r.MatchString(dn)
|
||||||
|
}
|
||||||
|
|||||||
@ -11,8 +11,10 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
"github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
@ -23,6 +25,127 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
const TestDN = "/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=4eca0d46a2324036b0b326dc58cfc802-user"
|
||||||
|
|
||||||
|
type RestoreMailUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestoreMailUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &RestoreMailUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreMailUnitSuite) TestIsValidEmail() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
email string
|
||||||
|
check assert.BoolAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid email",
|
||||||
|
email: "foo@bar.com",
|
||||||
|
check: assert.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid email, missing domain",
|
||||||
|
email: "foo.com",
|
||||||
|
check: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid email, random uuid",
|
||||||
|
email: "12345678-abcd-90ef-88f8-2d95ef12fb66",
|
||||||
|
check: assert.False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty email",
|
||||||
|
email: "",
|
||||||
|
check: assert.False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
result := isValidEmail(test.email)
|
||||||
|
test.check(t, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreMailUnitSuite) TestIsValidDN() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
dn string
|
||||||
|
check assert.BoolAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid DN",
|
||||||
|
dn: TestDN,
|
||||||
|
check: assert.True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid DN",
|
||||||
|
dn: "random string",
|
||||||
|
check: assert.False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
result := isValidDN(test.dn)
|
||||||
|
test.check(t, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RestoreMailUnitSuite) TestSetReplyTos() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
replyTos := make([]models.Recipientable, 0)
|
||||||
|
|
||||||
|
emailAddresses := map[string]string{
|
||||||
|
"foo.bar": "foo@bar.com",
|
||||||
|
"foo.com": "foo.com",
|
||||||
|
"empty": "",
|
||||||
|
"dn": TestDN,
|
||||||
|
}
|
||||||
|
|
||||||
|
validEmailAddresses := map[string]string{
|
||||||
|
"foo.bar": "foo@bar.com",
|
||||||
|
"dn": TestDN,
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range emailAddresses {
|
||||||
|
emailAddress := models.NewEmailAddress()
|
||||||
|
emailAddress.SetAddress(ptr.To(v))
|
||||||
|
emailAddress.SetName(ptr.To(k))
|
||||||
|
|
||||||
|
replyTo := models.NewRecipient()
|
||||||
|
replyTo.SetEmailAddress(emailAddress)
|
||||||
|
|
||||||
|
replyTos = append(replyTos, replyTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
mailMessage := models.NewMessage()
|
||||||
|
mailMessage.SetReplyTo(replyTos)
|
||||||
|
|
||||||
|
setReplyTos(mailMessage)
|
||||||
|
|
||||||
|
sanitizedReplyTos := mailMessage.GetReplyTo()
|
||||||
|
require.Len(t, sanitizedReplyTos, len(validEmailAddresses))
|
||||||
|
|
||||||
|
for _, sanitizedReplyTo := range sanitizedReplyTos {
|
||||||
|
emailAddress := sanitizedReplyTo.GetEmailAddress()
|
||||||
|
|
||||||
|
assert.Contains(t, validEmailAddresses, ptr.Val(emailAddress.GetName()))
|
||||||
|
assert.Equal(t, validEmailAddresses[ptr.Val(emailAddress.GetName())], ptr.Val(emailAddress.GetAddress()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var _ mailRestorer = &mailRestoreMock{}
|
var _ mailRestorer = &mailRestoreMock{}
|
||||||
|
|
||||||
type mailRestoreMock struct {
|
type mailRestoreMock struct {
|
||||||
@ -72,7 +195,7 @@ func (m *mailRestoreMock) PostLargeAttachment(
|
|||||||
|
|
||||||
type MailRestoreIntgSuite struct {
|
type MailRestoreIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its intgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMailRestoreIntgSuite(t *testing.T) {
|
func TestMailRestoreIntgSuite(t *testing.T) {
|
||||||
@ -84,16 +207,16 @@ func TestMailRestoreIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *MailRestoreIntgSuite) SetupSuite() {
|
func (suite *MailRestoreIntgSuite) SetupSuite() {
|
||||||
suite.its = newIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *MailRestoreIntgSuite) TestCreateContainerDestination() {
|
func (suite *MailRestoreIntgSuite) TestCreateContainerDestination() {
|
||||||
runCreateDestinationTest(
|
runCreateDestinationTest(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
newMailRestoreHandler(suite.its.ac),
|
newMailRestoreHandler(suite.m365.AC),
|
||||||
path.EmailCategory,
|
path.EmailCategory,
|
||||||
suite.its.creds.AzureTenantID,
|
suite.m365.TenantID,
|
||||||
suite.its.userID,
|
suite.m365.User.ID,
|
||||||
testdata.DefaultRestoreConfig("").Location,
|
testdata.DefaultRestoreConfig("").Location,
|
||||||
[]string{"Griffindor", "Croix"},
|
[]string{"Griffindor", "Croix"},
|
||||||
[]string{"Griffindor", "Felicius"})
|
[]string{"Griffindor", "Felicius"})
|
||||||
@ -234,7 +357,7 @@ func (suite *MailRestoreIntgSuite) TestRestoreMail() {
|
|||||||
ctx,
|
ctx,
|
||||||
test.apiMock,
|
test.apiMock,
|
||||||
body,
|
body,
|
||||||
suite.its.userID,
|
suite.m365.User.ID,
|
||||||
"destination",
|
"destination",
|
||||||
test.collisionMap,
|
test.collisionMap,
|
||||||
test.onCollision,
|
test.onCollision,
|
||||||
|
|||||||
@ -6,10 +6,15 @@ import (
|
|||||||
"github.com/microsoft/kiota-abstractions-go/serialization"
|
"github.com/microsoft/kiota-abstractions-go/serialization"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// get and serialize item mock
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type ItemGetSerialize struct {
|
type ItemGetSerialize struct {
|
||||||
GetData serialization.Parsable
|
GetData serialization.Parsable
|
||||||
GetCount int
|
GetCount int
|
||||||
@ -44,3 +49,23 @@ func (m *ItemGetSerialize) Serialize(
|
|||||||
func DefaultItemGetSerialize() *ItemGetSerialize {
|
func DefaultItemGetSerialize() *ItemGetSerialize {
|
||||||
return &ItemGetSerialize{}
|
return &ItemGetSerialize{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// can skip item failure mock
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type canSkipFailChecker struct {
|
||||||
|
canSkip bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m canSkipFailChecker) CanSkipItemFailure(
|
||||||
|
err error,
|
||||||
|
resourceID string,
|
||||||
|
opts control.Options,
|
||||||
|
) (fault.SkipCause, bool) {
|
||||||
|
return fault.SkipCause("testing"), m.canSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
func NeverCanSkipFailChecker() *canSkipFailChecker {
|
||||||
|
return &canSkipFailChecker{}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
exchMock "github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
exchMock "github.com/alcionai/corso/src/internal/m365/service/exchange/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
@ -24,8 +24,7 @@ import (
|
|||||||
|
|
||||||
type RestoreIntgSuite struct {
|
type RestoreIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
credentials account.M365Config
|
m365 its.M365IntgTestSetup
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRestoreIntgSuite(t *testing.T) {
|
func TestRestoreIntgSuite(t *testing.T) {
|
||||||
@ -37,18 +36,7 @@ func TestRestoreIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *RestoreIntgSuite) SetupSuite() {
|
func (suite *RestoreIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
m365, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.credentials = m365
|
|
||||||
suite.ac, err = api.NewClient(
|
|
||||||
m365,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRestoreContact ensures contact object can be created, placed into
|
// TestRestoreContact ensures contact object can be created, placed into
|
||||||
@ -60,26 +48,26 @@ func (suite *RestoreIntgSuite) TestRestoreContact() {
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
userID = tconfig.M365UserID(t)
|
|
||||||
folderName = testdata.DefaultRestoreConfig("contact").Location
|
folderName = testdata.DefaultRestoreConfig("contact").Location
|
||||||
handler = newContactRestoreHandler(suite.ac)
|
handler = newContactRestoreHandler(suite.m365.AC)
|
||||||
)
|
)
|
||||||
|
|
||||||
aFolder, err := handler.ac.CreateContainer(ctx, userID, "", folderName)
|
aFolder, err := handler.ac.CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
folderID := ptr.Val(aFolder.GetId())
|
folderID := ptr.Val(aFolder.GetId())
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Remove the folder containing contact prior to exiting test
|
// Remove the folder containing contact prior to exiting test
|
||||||
err = suite.ac.Contacts().DeleteContainer(ctx, userID, folderID)
|
err = suite.m365.AC.Contacts().DeleteContainer(ctx, suite.m365.User.ID, folderID)
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
info, err := handler.restore(
|
info, err := handler.restore(
|
||||||
ctx,
|
ctx,
|
||||||
exchMock.ContactBytes("Corso TestContact"),
|
exchMock.ContactBytes("Corso TestContact"),
|
||||||
userID, folderID,
|
suite.m365.User.ID,
|
||||||
|
folderID,
|
||||||
nil,
|
nil,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
fault.New(true),
|
fault.New(true),
|
||||||
@ -97,19 +85,18 @@ func (suite *RestoreIntgSuite) TestRestoreEvent() {
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
userID = tconfig.M365UserID(t)
|
|
||||||
subject = testdata.DefaultRestoreConfig("event").Location
|
subject = testdata.DefaultRestoreConfig("event").Location
|
||||||
handler = newEventRestoreHandler(suite.ac)
|
handler = newEventRestoreHandler(suite.m365.AC)
|
||||||
)
|
)
|
||||||
|
|
||||||
calendar, err := handler.ac.CreateContainer(ctx, userID, "", subject)
|
calendar, err := handler.ac.CreateContainer(ctx, suite.m365.User.ID, "", subject)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
calendarID := ptr.Val(calendar.GetId())
|
calendarID := ptr.Val(calendar.GetId())
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Removes calendar containing events created during the test
|
// Removes calendar containing events created during the test
|
||||||
err = suite.ac.Events().DeleteContainer(ctx, userID, calendarID)
|
err = suite.m365.AC.Events().DeleteContainer(ctx, suite.m365.User.ID, calendarID)
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -154,7 +141,8 @@ func (suite *RestoreIntgSuite) TestRestoreEvent() {
|
|||||||
info, err := handler.restore(
|
info, err := handler.restore(
|
||||||
ctx,
|
ctx,
|
||||||
test.bytes,
|
test.bytes,
|
||||||
userID, calendarID,
|
suite.m365.User.ID,
|
||||||
|
calendarID,
|
||||||
nil,
|
nil,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
fault.New(true),
|
fault.New(true),
|
||||||
@ -168,10 +156,7 @@ func (suite *RestoreIntgSuite) TestRestoreEvent() {
|
|||||||
// TestRestoreExchangeObject verifies path.Category usage for restored objects
|
// TestRestoreExchangeObject verifies path.Category usage for restored objects
|
||||||
func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
handlers := RestoreHandlers(suite.m365.AC)
|
||||||
handlers := RestoreHandlers(suite.ac)
|
|
||||||
|
|
||||||
userID := tconfig.M365UserID(suite.T())
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -186,7 +171,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("mailobj").Location
|
folderName := testdata.DefaultRestoreConfig("mailobj").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -199,7 +184,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("mailwattch").Location
|
folderName := testdata.DefaultRestoreConfig("mailwattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -212,7 +197,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("eventwattch").Location
|
folderName := testdata.DefaultRestoreConfig("eventwattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -225,7 +210,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("mailitemattch").Location
|
folderName := testdata.DefaultRestoreConfig("mailitemattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -240,7 +225,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("mailbasicattch").Location
|
folderName := testdata.DefaultRestoreConfig("mailbasicattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -255,7 +240,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("mailnestattch").Location
|
folderName := testdata.DefaultRestoreConfig("mailnestattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -270,7 +255,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("mailcontactattch").Location
|
folderName := testdata.DefaultRestoreConfig("mailcontactattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -283,7 +268,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("nestedattch").Location
|
folderName := testdata.DefaultRestoreConfig("nestedattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -296,7 +281,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("maillargeattch").Location
|
folderName := testdata.DefaultRestoreConfig("maillargeattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -309,7 +294,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("mailtwoattch").Location
|
folderName := testdata.DefaultRestoreConfig("mailtwoattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -322,7 +307,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("mailrefattch").Location
|
folderName := testdata.DefaultRestoreConfig("mailrefattch").Location
|
||||||
folder, err := handlers[path.EmailCategory].
|
folder, err := handlers[path.EmailCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -335,7 +320,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("contact").Location
|
folderName := testdata.DefaultRestoreConfig("contact").Location
|
||||||
folder, err := handlers[path.ContactsCategory].
|
folder, err := handlers[path.ContactsCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(folder.GetId())
|
return ptr.Val(folder.GetId())
|
||||||
@ -348,7 +333,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("event").Location
|
folderName := testdata.DefaultRestoreConfig("event").Location
|
||||||
calendar, err := handlers[path.EventsCategory].
|
calendar, err := handlers[path.EventsCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(calendar.GetId())
|
return ptr.Val(calendar.GetId())
|
||||||
@ -361,7 +346,7 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
destination: func(t *testing.T, ctx context.Context) string {
|
destination: func(t *testing.T, ctx context.Context) string {
|
||||||
folderName := testdata.DefaultRestoreConfig("eventobj").Location
|
folderName := testdata.DefaultRestoreConfig("eventobj").Location
|
||||||
calendar, err := handlers[path.EventsCategory].
|
calendar, err := handlers[path.EventsCategory].
|
||||||
CreateContainer(ctx, userID, "", folderName)
|
CreateContainer(ctx, suite.m365.User.ID, "", folderName)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
return ptr.Val(calendar.GetId())
|
return ptr.Val(calendar.GetId())
|
||||||
@ -380,7 +365,8 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
info, err := handlers[test.category].restore(
|
info, err := handlers[test.category].restore(
|
||||||
ctx,
|
ctx,
|
||||||
test.bytes,
|
test.bytes,
|
||||||
userID, destination,
|
suite.m365.User.ID,
|
||||||
|
destination,
|
||||||
nil,
|
nil,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
fault.New(true),
|
fault.New(true),
|
||||||
@ -400,12 +386,11 @@ func (suite *RestoreIntgSuite) TestRestoreAndBackupEvent_recurringInstancesWithA
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
userID = tconfig.M365UserID(t)
|
|
||||||
subject = testdata.DefaultRestoreConfig("event").Location
|
subject = testdata.DefaultRestoreConfig("event").Location
|
||||||
handler = newEventRestoreHandler(suite.ac)
|
handler = newEventRestoreHandler(suite.m365.AC)
|
||||||
)
|
)
|
||||||
|
|
||||||
calendar, err := handler.ac.CreateContainer(ctx, userID, "", subject)
|
calendar, err := handler.ac.CreateContainer(ctx, suite.m365.User.ID, "", subject)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
calendarID := ptr.Val(calendar.GetId())
|
calendarID := ptr.Val(calendar.GetId())
|
||||||
@ -414,7 +399,8 @@ func (suite *RestoreIntgSuite) TestRestoreAndBackupEvent_recurringInstancesWithA
|
|||||||
info, err := handler.restore(
|
info, err := handler.restore(
|
||||||
ctx,
|
ctx,
|
||||||
bytes,
|
bytes,
|
||||||
userID, calendarID,
|
suite.m365.User.ID,
|
||||||
|
calendarID,
|
||||||
nil,
|
nil,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
fault.New(true),
|
fault.New(true),
|
||||||
@ -425,7 +411,7 @@ func (suite *RestoreIntgSuite) TestRestoreAndBackupEvent_recurringInstancesWithA
|
|||||||
ec, err := handler.ac.Stable.
|
ec, err := handler.ac.Stable.
|
||||||
Client().
|
Client().
|
||||||
Users().
|
Users().
|
||||||
ByUserId(userID).
|
ByUserId(suite.m365.User.ID).
|
||||||
Calendars().
|
Calendars().
|
||||||
ByCalendarId(calendarID).
|
ByCalendarId(calendarID).
|
||||||
Events().
|
Events().
|
||||||
@ -435,17 +421,25 @@ func (suite *RestoreIntgSuite) TestRestoreAndBackupEvent_recurringInstancesWithA
|
|||||||
evts := ec.GetValue()
|
evts := ec.GetValue()
|
||||||
assert.Len(t, evts, 1, "count of events")
|
assert.Len(t, evts, 1, "count of events")
|
||||||
|
|
||||||
sp, info, err := suite.ac.Events().GetItem(ctx, userID, ptr.Val(evts[0].GetId()), fault.New(true))
|
sp, info, err := suite.m365.AC.Events().GetItem(
|
||||||
|
ctx,
|
||||||
|
suite.m365.User.ID,
|
||||||
|
ptr.Val(evts[0].GetId()),
|
||||||
|
fault.New(true))
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, info, "event item info")
|
assert.NotNil(t, info, "event item info")
|
||||||
|
|
||||||
body, err := suite.ac.Events().Serialize(ctx, sp, userID, ptr.Val(evts[0].GetId()))
|
body, err := suite.m365.AC.Events().Serialize(
|
||||||
|
ctx,
|
||||||
|
sp,
|
||||||
|
suite.m365.User.ID,
|
||||||
|
ptr.Val(evts[0].GetId()))
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
event, err := api.BytesToEventable(body)
|
event, err := api.BytesToEventable(body)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
assert.NotNil(t, event.GetRecurrence(), "recurrence")
|
assert.NotNil(t, event.GetRecurrence(), "recurrence")
|
||||||
|
|
||||||
eo := event.GetAdditionalData()["exceptionOccurrences"]
|
eo := event.GetAdditionalData()["exceptionOccurrences"]
|
||||||
assert.NotNil(t, eo, "exceptionOccurrences")
|
assert.NotNil(t, eo, "exceptionOccurrences")
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
@ -964,9 +965,7 @@ func (suite *BackupUnitSuite) TestPopulateCollections_ConversationsIncremental()
|
|||||||
|
|
||||||
type BackupIntgSuite struct {
|
type BackupIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
resource string
|
m365 its.M365IntgTestSetup
|
||||||
tenantID string
|
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupIntgSuite(t *testing.T) {
|
func TestBackupIntgSuite(t *testing.T) {
|
||||||
@ -979,32 +978,19 @@ func TestBackupIntgSuite(t *testing.T) {
|
|||||||
|
|
||||||
func (suite *BackupIntgSuite) SetupSuite() {
|
func (suite *BackupIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
||||||
|
|
||||||
suite.resource = tconfig.M365TeamID(t)
|
|
||||||
|
|
||||||
acct := tconfig.NewM365Account(t)
|
|
||||||
creds, err := acct.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.tenantID = creds.AzureTenantID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupIntgSuite) TestCreateCollections() {
|
func (suite *BackupIntgSuite) TestCreateCollections() {
|
||||||
var (
|
var (
|
||||||
protectedResource = tconfig.M365TeamID(suite.T())
|
protectedResource = suite.m365.Group.ID
|
||||||
resources = []string{protectedResource}
|
resources = []string{suite.m365.Group.ID}
|
||||||
handler = NewChannelBackupHandler(protectedResource, suite.ac.Channels())
|
handler = NewChannelBackupHandler(protectedResource, suite.m365.AC.Channels())
|
||||||
)
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -1030,13 +1016,13 @@ func (suite *BackupIntgSuite) TestCreateCollections() {
|
|||||||
|
|
||||||
ctrlOpts := control.DefaultOptions()
|
ctrlOpts := control.DefaultOptions()
|
||||||
|
|
||||||
sel := selectors.NewGroupsBackup([]string{protectedResource})
|
sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
|
||||||
sel.Include(selTD.GroupsBackupChannelScope(sel))
|
sel.Include(selTD.GroupsBackupChannelScope(sel))
|
||||||
|
|
||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: ctrlOpts,
|
Options: ctrlOpts,
|
||||||
ProtectedResource: inMock.NewProvider(protectedResource, protectedResource),
|
ProtectedResource: suite.m365.Group.Provider,
|
||||||
Selector: sel.Selector,
|
Selector: sel.Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1044,7 +1030,7 @@ func (suite *BackupIntgSuite) TestCreateCollections() {
|
|||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
handler,
|
handler,
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
test.scope,
|
test.scope,
|
||||||
func(status *support.ControllerOperationStatus) {},
|
func(status *support.ControllerOperationStatus) {},
|
||||||
false,
|
false,
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
@ -328,59 +329,49 @@ func (suite *SharePointBackupUnitSuite) TestPopulateListsCollections_incremental
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SharePointSuite struct {
|
type SharePointBackupIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSharePointSuite(t *testing.T) {
|
func TestSharePointSuite(t *testing.T) {
|
||||||
suite.Run(t, &SharePointSuite{
|
suite.Run(t, &SharePointBackupIntgSuite{
|
||||||
Suite: tester.NewIntegrationSuite(
|
Suite: tester.NewIntegrationSuite(
|
||||||
t,
|
t,
|
||||||
[][]string{tconfig.M365AcctCredEnvs}),
|
[][]string{tconfig.M365AcctCredEnvs}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointSuite) SetupSuite() {
|
func (suite *SharePointBackupIntgSuite) SetupSuite() {
|
||||||
ctx, flush := tester.NewContext(suite.T())
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, false, 4)
|
graph.InitializeConcurrencyLimiter(ctx, false, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointSuite) TestCollectPages() {
|
func (suite *SharePointBackupIntgSuite) TestCollectPages() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
counter := count.New()
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
|
||||||
siteID = tconfig.M365SiteID(t)
|
|
||||||
a = tconfig.NewM365Account(t)
|
|
||||||
counter = count.New()
|
|
||||||
)
|
|
||||||
|
|
||||||
creds, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
ac, err := api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
counter)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: mock.NewProvider(siteID, siteID),
|
ProtectedResource: suite.m365.Site.Provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
sel := selectors.NewSharePointBackup([]string{siteID})
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
|
|
||||||
col, err := CollectPages(
|
col, err := CollectPages(
|
||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
creds,
|
suite.m365.Creds,
|
||||||
ac,
|
suite.m365.AC,
|
||||||
sel.Lists(selectors.Any())[0],
|
sel.Lists(selectors.Any())[0],
|
||||||
(&MockGraphService{}).UpdateStatus,
|
(&MockGraphService{}).UpdateStatus,
|
||||||
counter,
|
counter,
|
||||||
@ -389,43 +380,27 @@ func (suite *SharePointSuite) TestCollectPages() {
|
|||||||
assert.NotEmpty(t, col)
|
assert.NotEmpty(t, col)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointSuite) TestCollectLists() {
|
func (suite *SharePointBackupIntgSuite) TestCollectLists() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
|
||||||
siteID = tconfig.M365SiteID(t)
|
|
||||||
a = tconfig.NewM365Account(t)
|
|
||||||
counter = count.New()
|
|
||||||
)
|
|
||||||
|
|
||||||
creds, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
ac, err := api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
counter)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: control.DefaultOptions(),
|
Options: control.DefaultOptions(),
|
||||||
ProtectedResource: mock.NewProvider(siteID, siteID),
|
ProtectedResource: suite.m365.Site.Provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
sel := selectors.NewSharePointBackup([]string{siteID})
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
|
bh := NewListsBackupHandler(suite.m365.Site.ID, suite.m365.AC.Lists())
|
||||||
bh := NewListsBackupHandler(bpc.ProtectedResource.ID(), ac.Lists())
|
|
||||||
|
|
||||||
col, _, err := CollectLists(
|
col, _, err := CollectLists(
|
||||||
ctx,
|
ctx,
|
||||||
bh,
|
bh,
|
||||||
bpc,
|
bpc,
|
||||||
ac,
|
suite.m365.AC,
|
||||||
creds.AzureTenantID,
|
suite.m365.Creds.AzureTenantID,
|
||||||
sel.Lists(selectors.Any())[0],
|
sel.Lists(selectors.Any())[0],
|
||||||
(&MockGraphService{}).UpdateStatus,
|
(&MockGraphService{}).UpdateStatus,
|
||||||
count.New(),
|
count.New(),
|
||||||
@ -445,7 +420,7 @@ func (suite *SharePointSuite) TestCollectLists() {
|
|||||||
assert.True(t, metadataFound)
|
assert.True(t, metadataFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointSuite) TestParseListsMetadataCollections() {
|
func (suite *SharePointBackupIntgSuite) TestParseListsMetadataCollections() {
|
||||||
type fileValues struct {
|
type fileValues struct {
|
||||||
fileName string
|
fileName string
|
||||||
value string
|
value string
|
||||||
@ -580,7 +555,7 @@ func (f failingColl) FetchItemByName(context.Context, string) (data.Item, error)
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointSuite) TestParseListsMetadataCollections_ReadFailure() {
|
func (suite *SharePointBackupIntgSuite) TestParseListsMetadataCollections_ReadFailure() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
spMock "github.com/alcionai/corso/src/internal/m365/service/sharepoint/mock"
|
spMock "github.com/alcionai/corso/src/internal/m365/service/sharepoint/mock"
|
||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
@ -113,34 +114,17 @@ func (suite *SharePointCollectionUnitSuite) TestPrefetchCollection_state() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SharePointCollectionSuite struct {
|
type SharePointCollIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
siteID string
|
m365 its.M365IntgTestSetup
|
||||||
creds account.M365Config
|
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointCollectionSuite) SetupSuite() {
|
func (suite *SharePointCollIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
|
||||||
suite.siteID = tconfig.M365SiteID(t)
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
m365, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.creds = m365
|
|
||||||
|
|
||||||
ac, err := api.NewClient(
|
|
||||||
m365,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.ac = ac
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSharePointCollectionSuite(t *testing.T) {
|
func TestSharePointCollectionSuite(t *testing.T) {
|
||||||
suite.Run(t, &SharePointCollectionSuite{
|
suite.Run(t, &SharePointCollIntgSuite{
|
||||||
Suite: tester.NewIntegrationSuite(
|
Suite: tester.NewIntegrationSuite(
|
||||||
t,
|
t,
|
||||||
[][]string{tconfig.M365AcctCredEnvs}),
|
[][]string{tconfig.M365AcctCredEnvs}),
|
||||||
@ -149,15 +133,13 @@ func TestSharePointCollectionSuite(t *testing.T) {
|
|||||||
|
|
||||||
// TestListCollection tests basic functionality to create
|
// TestListCollection tests basic functionality to create
|
||||||
// SharePoint collection and to use the data stream channel.
|
// SharePoint collection and to use the data stream channel.
|
||||||
func (suite *SharePointCollectionSuite) TestPrefetchCollection_Items() {
|
func (suite *SharePointCollIntgSuite) TestPrefetchCollection_Items() {
|
||||||
var (
|
var (
|
||||||
tenant = "some"
|
|
||||||
user = "user"
|
|
||||||
prevRoot = "prev"
|
prevRoot = "prev"
|
||||||
dirRoot = "directory"
|
dirRoot = "directory"
|
||||||
)
|
)
|
||||||
|
|
||||||
sel := selectors.NewSharePointBackup([]string{"site"})
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
|
|
||||||
tables := []struct {
|
tables := []struct {
|
||||||
name, itemName string
|
name, itemName string
|
||||||
@ -183,8 +165,8 @@ func (suite *SharePointCollectionSuite) TestPrefetchCollection_Items() {
|
|||||||
getter: &mock.ListHandler{},
|
getter: &mock.ListHandler{},
|
||||||
getDir: func(t *testing.T, root string) path.Path {
|
getDir: func(t *testing.T, root string) path.Path {
|
||||||
dir, err := path.Build(
|
dir, err := path.Build(
|
||||||
tenant,
|
suite.m365.TenantID,
|
||||||
user,
|
suite.m365.User.ID,
|
||||||
path.SharePointService,
|
path.SharePointService,
|
||||||
path.ListsCategory,
|
path.ListsCategory,
|
||||||
false,
|
false,
|
||||||
@ -232,8 +214,8 @@ func (suite *SharePointCollectionSuite) TestPrefetchCollection_Items() {
|
|||||||
getter: nil,
|
getter: nil,
|
||||||
getDir: func(t *testing.T, root string) path.Path {
|
getDir: func(t *testing.T, root string) path.Path {
|
||||||
dir, err := path.Build(
|
dir, err := path.Build(
|
||||||
tenant,
|
suite.m365.TenantID,
|
||||||
user,
|
suite.m365.User.ID,
|
||||||
path.SharePointService,
|
path.SharePointService,
|
||||||
path.PagesCategory,
|
path.PagesCategory,
|
||||||
false,
|
false,
|
||||||
@ -270,7 +252,7 @@ func (suite *SharePointCollectionSuite) TestPrefetchCollection_Items() {
|
|||||||
test.getDir(t, test.curr),
|
test.getDir(t, test.curr),
|
||||||
test.getDir(t, test.prev),
|
test.getDir(t, test.prev),
|
||||||
test.locPb,
|
test.locPb,
|
||||||
suite.ac,
|
suite.m365.AC,
|
||||||
test.scope,
|
test.scope,
|
||||||
nil,
|
nil,
|
||||||
control.DefaultOptions(),
|
control.DefaultOptions(),
|
||||||
@ -306,7 +288,7 @@ func (suite *SharePointCollectionSuite) TestPrefetchCollection_Items() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointCollectionSuite) TestLazyCollection_Items() {
|
func (suite *SharePointCollIntgSuite) TestLazyCollection_Items() {
|
||||||
var (
|
var (
|
||||||
t = suite.T()
|
t = suite.T()
|
||||||
errs = fault.New(true)
|
errs = fault.New(true)
|
||||||
@ -416,7 +398,7 @@ func (suite *SharePointCollectionSuite) TestLazyCollection_Items() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointCollectionSuite) TestLazyItem() {
|
func (suite *SharePointCollIntgSuite) TestLazyItem() {
|
||||||
var (
|
var (
|
||||||
t = suite.T()
|
t = suite.T()
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
@ -460,7 +442,7 @@ func (suite *SharePointCollectionSuite) TestLazyItem() {
|
|||||||
assert.Equal(t, now, info.Modified())
|
assert.Equal(t, now, info.Modified())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointCollectionSuite) TestLazyItem_ReturnsEmptyReaderOnDeletedInFlight() {
|
func (suite *SharePointCollIntgSuite) TestLazyItem_ReturnsEmptyReaderOnDeletedInFlight() {
|
||||||
var (
|
var (
|
||||||
t = suite.T()
|
t = suite.T()
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
|
|||||||
@ -21,8 +21,8 @@ import (
|
|||||||
siteMock "github.com/alcionai/corso/src/internal/m365/collection/site/mock"
|
siteMock "github.com/alcionai/corso/src/internal/m365/collection/site/mock"
|
||||||
spMock "github.com/alcionai/corso/src/internal/m365/service/sharepoint/mock"
|
spMock "github.com/alcionai/corso/src/internal/m365/service/sharepoint/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
@ -87,32 +87,17 @@ func (suite *SharePointCollectionUnitSuite) TestFormatListsRestoreDestination()
|
|||||||
|
|
||||||
type SharePointRestoreSuite struct {
|
type SharePointRestoreSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
siteID string
|
m365 its.M365IntgTestSetup
|
||||||
creds account.M365Config
|
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointRestoreSuite) SetupSuite() {
|
func (suite *SharePointRestoreSuite) SetupSuite() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, false, 4)
|
graph.InitializeConcurrencyLimiter(ctx, false, 4)
|
||||||
|
|
||||||
suite.siteID = tconfig.M365SiteID(t)
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
m365, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.creds = m365
|
|
||||||
|
|
||||||
ac, err := api.NewClient(
|
|
||||||
m365,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.ac = ac
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSharePointRestoreSuite(t *testing.T) {
|
func TestSharePointRestoreSuite(t *testing.T) {
|
||||||
@ -135,8 +120,8 @@ func (suite *SharePointRestoreSuite) TestListCollection_Restore() {
|
|||||||
listTemplate = "genericList"
|
listTemplate = "genericList"
|
||||||
restoreCfg = testdata.DefaultRestoreConfig("")
|
restoreCfg = testdata.DefaultRestoreConfig("")
|
||||||
destName = restoreCfg.Location
|
destName = restoreCfg.Location
|
||||||
lrh = NewListsRestoreHandler(suite.siteID, suite.ac.Lists())
|
lrh = NewListsRestoreHandler(suite.m365.Site.ID, suite.m365.AC.Lists())
|
||||||
service = createTestService(t, suite.creds)
|
service = createTestService(t, suite.m365.Creds)
|
||||||
list = stubList(listTemplate, listName)
|
list = stubList(listTemplate, listName)
|
||||||
mockData = generateListData(t, service, list)
|
mockData = generateListData(t, service, list)
|
||||||
)
|
)
|
||||||
@ -147,7 +132,7 @@ func (suite *SharePointRestoreSuite) TestListCollection_Restore() {
|
|||||||
ctx,
|
ctx,
|
||||||
lrh,
|
lrh,
|
||||||
mockData,
|
mockData,
|
||||||
suite.siteID,
|
suite.m365.Site.ID,
|
||||||
restoreCfg,
|
restoreCfg,
|
||||||
nil,
|
nil,
|
||||||
count.New(),
|
count.New(),
|
||||||
@ -156,7 +141,7 @@ func (suite *SharePointRestoreSuite) TestListCollection_Restore() {
|
|||||||
assert.Equal(t, fmt.Sprintf("%s_%s", destName, listName), deets.SharePoint.List.Name)
|
assert.Equal(t, fmt.Sprintf("%s_%s", destName, listName), deets.SharePoint.List.Name)
|
||||||
|
|
||||||
// Clean-Up
|
// Clean-Up
|
||||||
deleteList(ctx, t, suite.siteID, lrh, deets)
|
deleteList(ctx, t, suite.m365.Site.ID, lrh, deets)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointRestoreSuite) TestListCollection_Restore_invalidListTemplate() {
|
func (suite *SharePointRestoreSuite) TestListCollection_Restore_invalidListTemplate() {
|
||||||
@ -166,10 +151,10 @@ func (suite *SharePointRestoreSuite) TestListCollection_Restore_invalidListTempl
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
lrh = NewListsRestoreHandler(suite.siteID, suite.ac.Lists())
|
lrh = NewListsRestoreHandler(suite.m365.Site.ID, suite.m365.AC.Lists())
|
||||||
listName = "MockListing"
|
listName = "MockListing"
|
||||||
restoreCfg = testdata.DefaultRestoreConfig("")
|
restoreCfg = testdata.DefaultRestoreConfig("")
|
||||||
service = createTestService(t, suite.creds)
|
service = createTestService(t, suite.m365.Creds)
|
||||||
)
|
)
|
||||||
|
|
||||||
restoreCfg.OnCollision = control.Copy
|
restoreCfg.OnCollision = control.Copy
|
||||||
@ -201,7 +186,7 @@ func (suite *SharePointRestoreSuite) TestListCollection_Restore_invalidListTempl
|
|||||||
ctx,
|
ctx,
|
||||||
lrh,
|
lrh,
|
||||||
listData,
|
listData,
|
||||||
suite.siteID,
|
suite.m365.Site.ID,
|
||||||
restoreCfg,
|
restoreCfg,
|
||||||
nil,
|
nil,
|
||||||
count.New(),
|
count.New(),
|
||||||
@ -222,8 +207,8 @@ func (suite *SharePointRestoreSuite) TestListCollection_RestoreInPlace_skip() {
|
|||||||
listName = "MockListing"
|
listName = "MockListing"
|
||||||
listTemplate = "genericList"
|
listTemplate = "genericList"
|
||||||
restoreCfg = testdata.DefaultRestoreConfig("")
|
restoreCfg = testdata.DefaultRestoreConfig("")
|
||||||
lrh = NewListsRestoreHandler(suite.siteID, suite.ac.Lists())
|
lrh = NewListsRestoreHandler(suite.m365.Site.ID, suite.m365.AC.Lists())
|
||||||
service = createTestService(t, suite.creds)
|
service = createTestService(t, suite.m365.Creds)
|
||||||
list = stubList(listTemplate, listName)
|
list = stubList(listTemplate, listName)
|
||||||
newList = stubList(listTemplate, listName)
|
newList = stubList(listTemplate, listName)
|
||||||
cl = count.New()
|
cl = count.New()
|
||||||
@ -239,7 +224,7 @@ func (suite *SharePointRestoreSuite) TestListCollection_RestoreInPlace_skip() {
|
|||||||
ctx,
|
ctx,
|
||||||
lrh,
|
lrh,
|
||||||
mockData,
|
mockData,
|
||||||
suite.siteID,
|
suite.m365.Site.ID,
|
||||||
restoreCfg, // OnCollision is skip by default
|
restoreCfg, // OnCollision is skip by default
|
||||||
collisionKeyToItemID,
|
collisionKeyToItemID,
|
||||||
cl,
|
cl,
|
||||||
@ -261,7 +246,7 @@ func (suite *SharePointRestoreSuite) TestListCollection_RestoreInPlace_copy() {
|
|||||||
listTemplate = "genericList"
|
listTemplate = "genericList"
|
||||||
listID = "some-list-id"
|
listID = "some-list-id"
|
||||||
restoreCfg = testdata.DefaultRestoreConfig("")
|
restoreCfg = testdata.DefaultRestoreConfig("")
|
||||||
service = createTestService(t, suite.creds)
|
service = createTestService(t, suite.m365.Creds)
|
||||||
|
|
||||||
policyToKey = map[control.CollisionPolicy]count.Key{
|
policyToKey = map[control.CollisionPolicy]count.Key{
|
||||||
control.Replace: count.CollisionReplace,
|
control.Replace: count.CollisionReplace,
|
||||||
@ -354,7 +339,7 @@ func (suite *SharePointRestoreSuite) TestListCollection_RestoreInPlace_copy() {
|
|||||||
ctx,
|
ctx,
|
||||||
test.lrh,
|
test.lrh,
|
||||||
mockData,
|
mockData,
|
||||||
suite.siteID,
|
suite.m365.Site.ID,
|
||||||
restoreCfg,
|
restoreCfg,
|
||||||
collisionKeyToItemID,
|
collisionKeyToItemID,
|
||||||
cl,
|
cl,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
@ -255,9 +256,7 @@ func (suite *BackupUnitSuite) TestPopulateCollections() {
|
|||||||
|
|
||||||
type BackupIntgSuite struct {
|
type BackupIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
resource string
|
m365 its.M365IntgTestSetup
|
||||||
tenantID string
|
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupIntgSuite(t *testing.T) {
|
func TestBackupIntgSuite(t *testing.T) {
|
||||||
@ -270,33 +269,20 @@ func TestBackupIntgSuite(t *testing.T) {
|
|||||||
|
|
||||||
func (suite *BackupIntgSuite) SetupSuite() {
|
func (suite *BackupIntgSuite) SetupSuite() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
||||||
|
|
||||||
suite.resource = tconfig.M365TeamID(t)
|
|
||||||
|
|
||||||
acct := tconfig.NewM365Account(t)
|
|
||||||
creds, err := acct.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.tenantID = creds.AzureTenantID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupIntgSuite) TestCreateCollections() {
|
func (suite *BackupIntgSuite) TestCreateCollections() {
|
||||||
var (
|
var (
|
||||||
tenant = tconfig.M365TenantID(suite.T())
|
tenant = suite.m365.TenantID
|
||||||
protectedResource = tconfig.M365TeamID(suite.T())
|
protectedResource = suite.m365.Group.ID
|
||||||
resources = []string{protectedResource}
|
resources = []string{protectedResource}
|
||||||
handler = NewUsersChatsBackupHandler(tenant, protectedResource, suite.ac.Chats())
|
handler = NewUsersChatsBackupHandler(tenant, protectedResource, suite.m365.AC.Chats())
|
||||||
)
|
)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -322,13 +308,13 @@ func (suite *BackupIntgSuite) TestCreateCollections() {
|
|||||||
|
|
||||||
ctrlOpts := control.DefaultOptions()
|
ctrlOpts := control.DefaultOptions()
|
||||||
|
|
||||||
sel := selectors.NewTeamsChatsBackup([]string{protectedResource})
|
sel := selectors.NewTeamsChatsBackup([]string{suite.m365.Group.ID})
|
||||||
sel.Include(selTD.TeamsChatsBackupChatScope(sel))
|
sel.Include(selTD.TeamsChatsBackupChatScope(sel))
|
||||||
|
|
||||||
bpc := inject.BackupProducerConfig{
|
bpc := inject.BackupProducerConfig{
|
||||||
LastBackupVersion: version.NoBackup,
|
LastBackupVersion: version.NoBackup,
|
||||||
Options: ctrlOpts,
|
Options: ctrlOpts,
|
||||||
ProtectedResource: inMock.NewProvider(protectedResource, protectedResource),
|
ProtectedResource: suite.m365.Group.Provider,
|
||||||
Selector: sel.Selector,
|
Selector: sel.Selector,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +322,7 @@ func (suite *BackupIntgSuite) TestCreateCollections() {
|
|||||||
ctx,
|
ctx,
|
||||||
bpc,
|
bpc,
|
||||||
handler,
|
handler,
|
||||||
suite.tenantID,
|
suite.m365.TenantID,
|
||||||
test.scope,
|
test.scope,
|
||||||
func(status *support.ControllerOperationStatus) {},
|
func(status *support.ControllerOperationStatus) {},
|
||||||
false,
|
false,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
@ -414,8 +415,7 @@ func (suite *ControllerUnitSuite) TestController_CacheItemInfo() {
|
|||||||
type ControllerIntegrationSuite struct {
|
type ControllerIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
ctrl *Controller
|
ctrl *Controller
|
||||||
user string
|
m365 its.M365IntgTestSetup
|
||||||
secondaryUser string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestControllerIntegrationSuite(t *testing.T) {
|
func TestControllerIntegrationSuite(t *testing.T) {
|
||||||
@ -428,15 +428,12 @@ func TestControllerIntegrationSuite(t *testing.T) {
|
|||||||
|
|
||||||
func (suite *ControllerIntegrationSuite) SetupSuite() {
|
func (suite *ControllerIntegrationSuite) SetupSuite() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
suite.ctrl = newController(ctx, t, path.ExchangeService)
|
suite.ctrl = newController(ctx, t, path.ExchangeService)
|
||||||
suite.user = tconfig.M365UserID(t)
|
|
||||||
suite.secondaryUser = tconfig.SecondaryM365UserID(t)
|
|
||||||
|
|
||||||
tester.LogTimeOfTest(t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ControllerIntegrationSuite) TestEmptyCollections() {
|
func (suite *ControllerIntegrationSuite) TestEmptyCollections() {
|
||||||
@ -1064,7 +1061,7 @@ func (suite *ControllerIntegrationSuite) TestRestoreAndBackup_core() {
|
|||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
cfg := stub.ConfigInfo{
|
cfg := stub.ConfigInfo{
|
||||||
Tenant: suite.ctrl.tenant,
|
Tenant: suite.ctrl.tenant,
|
||||||
ResourceOwners: []string{suite.user},
|
ResourceOwners: []string{suite.m365.User.ID},
|
||||||
Service: test.service,
|
Service: test.service,
|
||||||
Opts: control.DefaultOptions(),
|
Opts: control.DefaultOptions(),
|
||||||
RestoreCfg: control.DefaultRestoreConfig(dttm.SafeForTesting),
|
RestoreCfg: control.DefaultRestoreConfig(dttm.SafeForTesting),
|
||||||
@ -1143,7 +1140,7 @@ func (suite *ControllerIntegrationSuite) TestMultiFolderBackupDifferentNames() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
restoreSel := getSelectorWith(t, test.service, []string{suite.user}, true)
|
restoreSel := getSelectorWith(t, test.service, []string{suite.m365.User.ID}, true)
|
||||||
expectedDests := make([]destAndCats, 0, len(test.collections))
|
expectedDests := make([]destAndCats, 0, len(test.collections))
|
||||||
allItems := 0
|
allItems := 0
|
||||||
allExpectedData := map[string]map[string][]byte{}
|
allExpectedData := map[string]map[string][]byte{}
|
||||||
@ -1154,7 +1151,7 @@ func (suite *ControllerIntegrationSuite) TestMultiFolderBackupDifferentNames() {
|
|||||||
restoreCfg.IncludePermissions = true
|
restoreCfg.IncludePermissions = true
|
||||||
|
|
||||||
expectedDests = append(expectedDests, destAndCats{
|
expectedDests = append(expectedDests, destAndCats{
|
||||||
resourceOwner: suite.user,
|
resourceOwner: suite.m365.User.ID,
|
||||||
dest: restoreCfg.Location,
|
dest: restoreCfg.Location,
|
||||||
cats: map[path.CategoryType]struct{}{
|
cats: map[path.CategoryType]struct{}{
|
||||||
collection.Category: {},
|
collection.Category: {},
|
||||||
@ -1164,7 +1161,7 @@ func (suite *ControllerIntegrationSuite) TestMultiFolderBackupDifferentNames() {
|
|||||||
totalItems, _, collections, expectedData, err := stub.CollectionsForInfo(
|
totalItems, _, collections, expectedData, err := stub.CollectionsForInfo(
|
||||||
test.service,
|
test.service,
|
||||||
suite.ctrl.tenant,
|
suite.ctrl.tenant,
|
||||||
suite.user,
|
suite.m365.User.ID,
|
||||||
restoreCfg,
|
restoreCfg,
|
||||||
[]stub.ColInfo{collection},
|
[]stub.ColInfo{collection},
|
||||||
version.Backup)
|
version.Backup)
|
||||||
@ -1289,7 +1286,7 @@ func (suite *ControllerIntegrationSuite) TestRestoreAndBackup_largeMailAttachmen
|
|||||||
|
|
||||||
cfg := stub.ConfigInfo{
|
cfg := stub.ConfigInfo{
|
||||||
Tenant: suite.ctrl.tenant,
|
Tenant: suite.ctrl.tenant,
|
||||||
ResourceOwners: []string{suite.user},
|
ResourceOwners: []string{suite.m365.User.ID},
|
||||||
Service: test.service,
|
Service: test.service,
|
||||||
Opts: control.DefaultOptions(),
|
Opts: control.DefaultOptions(),
|
||||||
RestoreCfg: restoreCfg,
|
RestoreCfg: restoreCfg,
|
||||||
@ -1310,7 +1307,7 @@ func (suite *ControllerIntegrationSuite) TestProduceBackupCollections_createsPre
|
|||||||
name: "Exchange",
|
name: "Exchange",
|
||||||
resourceCat: resource.Users,
|
resourceCat: resource.Users,
|
||||||
selectorFunc: func(t *testing.T) selectors.Selector {
|
selectorFunc: func(t *testing.T) selectors.Selector {
|
||||||
sel := selectors.NewExchangeBackup([]string{suite.user})
|
sel := selectors.NewExchangeBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(
|
sel.Include(
|
||||||
sel.ContactFolders(selectors.None()),
|
sel.ContactFolders(selectors.None()),
|
||||||
sel.EventCalendars(selectors.None()),
|
sel.EventCalendars(selectors.None()),
|
||||||
@ -1329,7 +1326,7 @@ func (suite *ControllerIntegrationSuite) TestProduceBackupCollections_createsPre
|
|||||||
name: "OneDrive",
|
name: "OneDrive",
|
||||||
resourceCat: resource.Users,
|
resourceCat: resource.Users,
|
||||||
selectorFunc: func(t *testing.T) selectors.Selector {
|
selectorFunc: func(t *testing.T) selectors.Selector {
|
||||||
sel := selectors.NewOneDriveBackup([]string{suite.user})
|
sel := selectors.NewOneDriveBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(sel.Folders(selectors.None()))
|
sel.Include(sel.Folders(selectors.None()))
|
||||||
|
|
||||||
return sel.Selector
|
return sel.Selector
|
||||||
|
|||||||
@ -1,30 +1,26 @@
|
|||||||
package m365
|
package m365
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
|
||||||
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
||||||
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
||||||
"github.com/alcionai/corso/src/internal/m365/service/onedrive/stub"
|
"github.com/alcionai/corso/src/internal/m365/service/onedrive/stub"
|
||||||
m365Stub "github.com/alcionai/corso/src/internal/m365/stub"
|
m365Stub "github.com/alcionai/corso/src/internal/m365/stub"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
"github.com/alcionai/corso/src/pkg/dttm"
|
"github.com/alcionai/corso/src/pkg/dttm"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -44,119 +40,6 @@ var (
|
|||||||
readPerm = []string{"read"}
|
readPerm = []string{"read"}
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustGetDefaultDriveID(
|
|
||||||
t *testing.T,
|
|
||||||
ctx context.Context, //revive:disable-line:context-as-argument
|
|
||||||
ac api.Client,
|
|
||||||
service path.ServiceType,
|
|
||||||
resourceOwner string,
|
|
||||||
) string {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
d models.Driveable
|
|
||||||
)
|
|
||||||
|
|
||||||
switch service {
|
|
||||||
case path.OneDriveService:
|
|
||||||
d, err = ac.Users().GetDefaultDrive(ctx, resourceOwner)
|
|
||||||
case path.SharePointService:
|
|
||||||
d, err = ac.Sites().GetDefaultDrive(ctx, resourceOwner)
|
|
||||||
default:
|
|
||||||
assert.FailNowf(t, "unknown service type %s", service.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = clues.Wrap(err, "retrieving drive")
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
id := ptr.Val(d.GetId())
|
|
||||||
require.NotEmpty(t, id)
|
|
||||||
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
type suiteInfo interface {
|
|
||||||
APIClient() api.Client
|
|
||||||
Tenant() string
|
|
||||||
// Returns (username, user ID) for the user. These values are used for
|
|
||||||
// permissions.
|
|
||||||
PrimaryUser() (string, string)
|
|
||||||
SecondaryUser() (string, string)
|
|
||||||
TertiaryUser() (string, string)
|
|
||||||
// ResourceOwner returns the resource owner to run the backup/restore
|
|
||||||
// with. This can be different from the values used for permissions and it can
|
|
||||||
// also be a site.
|
|
||||||
ResourceOwner() string
|
|
||||||
Service() path.ServiceType
|
|
||||||
}
|
|
||||||
|
|
||||||
type oneDriveSuite interface {
|
|
||||||
tester.Suite
|
|
||||||
suiteInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type suiteInfoImpl struct {
|
|
||||||
ac api.Client
|
|
||||||
controller *Controller
|
|
||||||
resourceOwner string
|
|
||||||
secondaryUser string
|
|
||||||
secondaryUserID string
|
|
||||||
service path.ServiceType
|
|
||||||
tertiaryUser string
|
|
||||||
tertiaryUserID string
|
|
||||||
user string
|
|
||||||
userID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSuiteInfoImpl(
|
|
||||||
t *testing.T,
|
|
||||||
ctx context.Context, //revive:disable-line:context-as-argument
|
|
||||||
resourceOwner string,
|
|
||||||
service path.ServiceType,
|
|
||||||
) suiteInfoImpl {
|
|
||||||
ctrl := newController(ctx, t, path.OneDriveService)
|
|
||||||
|
|
||||||
return suiteInfoImpl{
|
|
||||||
ac: ctrl.AC,
|
|
||||||
controller: ctrl,
|
|
||||||
resourceOwner: resourceOwner,
|
|
||||||
secondaryUser: tconfig.SecondaryM365UserID(t),
|
|
||||||
service: service,
|
|
||||||
tertiaryUser: tconfig.TertiaryM365UserID(t),
|
|
||||||
user: tconfig.M365UserID(t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (si suiteInfoImpl) APIClient() api.Client {
|
|
||||||
return si.ac
|
|
||||||
}
|
|
||||||
|
|
||||||
func (si suiteInfoImpl) Tenant() string {
|
|
||||||
return si.controller.tenant
|
|
||||||
}
|
|
||||||
|
|
||||||
func (si suiteInfoImpl) PrimaryUser() (string, string) {
|
|
||||||
return si.user, si.userID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (si suiteInfoImpl) SecondaryUser() (string, string) {
|
|
||||||
return si.secondaryUser, si.secondaryUserID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (si suiteInfoImpl) TertiaryUser() (string, string) {
|
|
||||||
return si.tertiaryUser, si.tertiaryUserID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (si suiteInfoImpl) ResourceOwner() string {
|
|
||||||
return si.resourceOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (si suiteInfoImpl) Service() path.ServiceType {
|
|
||||||
return si.service
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// SharePoint Libraries
|
// SharePoint Libraries
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -166,7 +49,8 @@ func (si suiteInfoImpl) Service() path.ServiceType {
|
|||||||
|
|
||||||
type SharePointIntegrationSuite struct {
|
type SharePointIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
suiteInfo
|
m365 its.M365IntgTestSetup
|
||||||
|
resourceAndSvc its.ResourceServicer
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSharePointIntegrationSuite(t *testing.T) {
|
func TestSharePointIntegrationSuite(t *testing.T) {
|
||||||
@ -178,57 +62,38 @@ func TestSharePointIntegrationSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointIntegrationSuite) SetupSuite() {
|
func (suite *SharePointIntegrationSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
suite.resourceAndSvc = its.NewResourceService(suite.m365.Site, path.SharePointService)
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
si := NewSuiteInfoImpl(suite.T(), ctx, tconfig.M365SiteID(suite.T()), path.SharePointService)
|
|
||||||
|
|
||||||
// users needed for permissions
|
|
||||||
user, err := si.controller.AC.Users().GetByID(ctx, si.user, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.user, clues.ToCore(err))
|
|
||||||
si.userID = ptr.Val(user.GetId())
|
|
||||||
|
|
||||||
secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.secondaryUser, clues.ToCore(err))
|
|
||||||
si.secondaryUserID = ptr.Val(secondaryUser.GetId())
|
|
||||||
|
|
||||||
tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.tertiaryUser, clues.ToCore(err))
|
|
||||||
si.tertiaryUserID = ptr.Val(tertiaryUser.GetId())
|
|
||||||
|
|
||||||
suite.suiteInfo = si
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointIntegrationSuite) TestRestoreAndBackup_MultipleFilesAndFolders_NoPermissions() {
|
func (suite *SharePointIntegrationSuite) TestRestoreAndBackup_MultipleFilesAndFolders_NoPermissions() {
|
||||||
testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(suite, version.Backup)
|
testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Re-enable these tests (disabled as it currently acting up CI)
|
// TODO: Re-enable these tests (disabled as it currently acting up CI)
|
||||||
func (suite *SharePointIntegrationSuite) TestPermissionsRestoreAndBackup() {
|
func (suite *SharePointIntegrationSuite) TestPermissionsRestoreAndBackup() {
|
||||||
suite.T().Skip("Temporarily disabled due to CI issues")
|
suite.T().Skip("Temporarily disabled due to CI issues")
|
||||||
testPermissionsRestoreAndBackup(suite, version.Backup)
|
testPermissionsRestoreAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointIntegrationSuite) TestRestoreNoPermissionsAndBackup() {
|
func (suite *SharePointIntegrationSuite) TestRestoreNoPermissionsAndBackup() {
|
||||||
suite.T().Skip("Temporarily disabled due to CI issues")
|
suite.T().Skip("Temporarily disabled due to CI issues")
|
||||||
testRestoreNoPermissionsAndBackup(suite, version.Backup)
|
testRestoreNoPermissionsAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointIntegrationSuite) TestPermissionsInheritanceRestoreAndBackup() {
|
func (suite *SharePointIntegrationSuite) TestPermissionsInheritanceRestoreAndBackup() {
|
||||||
suite.T().Skip("Temporarily disabled due to CI issues")
|
suite.T().Skip("Temporarily disabled due to CI issues")
|
||||||
testPermissionsInheritanceRestoreAndBackup(suite, version.Backup)
|
testPermissionsInheritanceRestoreAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointIntegrationSuite) TestLinkSharesInheritanceRestoreAndBackup() {
|
func (suite *SharePointIntegrationSuite) TestLinkSharesInheritanceRestoreAndBackup() {
|
||||||
suite.T().Skip("Temporarily disabled due to CI issues")
|
suite.T().Skip("Temporarily disabled due to CI issues")
|
||||||
testLinkSharesInheritanceRestoreAndBackup(suite, version.Backup)
|
testLinkSharesInheritanceRestoreAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointIntegrationSuite) TestRestoreFolderNamedFolderRegression() {
|
func (suite *SharePointIntegrationSuite) TestRestoreFolderNamedFolderRegression() {
|
||||||
// No reason why it couldn't work with previous versions, but this is when it got introduced.
|
// No reason why it couldn't work with previous versions, but this is when it got introduced.
|
||||||
testRestoreFolderNamedFolderRegression(suite, version.All8MigrateUserPNToID)
|
testRestoreFolderNamedFolderRegression(suite, suite.m365, suite.resourceAndSvc, version.All8MigrateUserPNToID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -236,7 +101,8 @@ func (suite *SharePointIntegrationSuite) TestRestoreFolderNamedFolderRegression(
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
type OneDriveIntegrationSuite struct {
|
type OneDriveIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
suiteInfo
|
m365 its.M365IntgTestSetup
|
||||||
|
resourceAndSvc its.ResourceServicer
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOneDriveIntegrationSuite(t *testing.T) {
|
func TestOneDriveIntegrationSuite(t *testing.T) {
|
||||||
@ -248,51 +114,33 @@ func TestOneDriveIntegrationSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveIntegrationSuite) SetupSuite() {
|
func (suite *OneDriveIntegrationSuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
suite.resourceAndSvc = its.NewResourceService(suite.m365.User, path.OneDriveService)
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
si := NewSuiteInfoImpl(t, ctx, tconfig.M365UserID(t), path.OneDriveService)
|
|
||||||
|
|
||||||
user, err := si.controller.AC.Users().GetByID(ctx, si.user, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.user, clues.ToCore(err))
|
|
||||||
si.userID = ptr.Val(user.GetId())
|
|
||||||
|
|
||||||
secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.secondaryUser, clues.ToCore(err))
|
|
||||||
si.secondaryUserID = ptr.Val(secondaryUser.GetId())
|
|
||||||
|
|
||||||
tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.tertiaryUser, clues.ToCore(err))
|
|
||||||
si.tertiaryUserID = ptr.Val(tertiaryUser.GetId())
|
|
||||||
|
|
||||||
suite.suiteInfo = si
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveIntegrationSuite) TestRestoreAndBackup_MultipleFilesAndFolders_NoPermissions() {
|
func (suite *OneDriveIntegrationSuite) TestRestoreAndBackup_MultipleFilesAndFolders_NoPermissions() {
|
||||||
testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(suite, version.Backup)
|
testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveIntegrationSuite) TestPermissionsRestoreAndBackup() {
|
func (suite *OneDriveIntegrationSuite) TestPermissionsRestoreAndBackup() {
|
||||||
testPermissionsRestoreAndBackup(suite, version.Backup)
|
testPermissionsRestoreAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveIntegrationSuite) TestRestoreNoPermissionsAndBackup() {
|
func (suite *OneDriveIntegrationSuite) TestRestoreNoPermissionsAndBackup() {
|
||||||
testRestoreNoPermissionsAndBackup(suite, version.Backup)
|
testRestoreNoPermissionsAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveIntegrationSuite) TestPermissionsInheritanceRestoreAndBackup() {
|
func (suite *OneDriveIntegrationSuite) TestPermissionsInheritanceRestoreAndBackup() {
|
||||||
testPermissionsInheritanceRestoreAndBackup(suite, version.Backup)
|
testPermissionsInheritanceRestoreAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveIntegrationSuite) TestLinkSharesInheritanceRestoreAndBackup() {
|
func (suite *OneDriveIntegrationSuite) TestLinkSharesInheritanceRestoreAndBackup() {
|
||||||
testLinkSharesInheritanceRestoreAndBackup(suite, version.Backup)
|
testLinkSharesInheritanceRestoreAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveIntegrationSuite) TestRestoreFolderNamedFolderRegression() {
|
func (suite *OneDriveIntegrationSuite) TestRestoreFolderNamedFolderRegression() {
|
||||||
// No reason why it couldn't work with previous versions, but this is when it got introduced.
|
// No reason why it couldn't work with previous versions, but this is when it got introduced.
|
||||||
testRestoreFolderNamedFolderRegression(suite, version.All8MigrateUserPNToID)
|
testRestoreFolderNamedFolderRegression(suite, suite.m365, suite.resourceAndSvc, version.All8MigrateUserPNToID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -300,7 +148,8 @@ func (suite *OneDriveIntegrationSuite) TestRestoreFolderNamedFolderRegression()
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
type OneDriveNightlySuite struct {
|
type OneDriveNightlySuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
suiteInfo
|
m365 its.M365IntgTestSetup
|
||||||
|
resourceAndSvc its.ResourceServicer
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOneDriveNightlySuite(t *testing.T) {
|
func TestOneDriveNightlySuite(t *testing.T) {
|
||||||
@ -312,70 +161,48 @@ func TestOneDriveNightlySuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveNightlySuite) SetupSuite() {
|
func (suite *OneDriveNightlySuite) SetupSuite() {
|
||||||
t := suite.T()
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
suite.resourceAndSvc = its.NewResourceService(suite.m365.User, path.OneDriveService)
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
si := NewSuiteInfoImpl(t, ctx, tconfig.M365UserID(t), path.OneDriveService)
|
|
||||||
|
|
||||||
user, err := si.controller.AC.Users().GetByID(ctx, si.user, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.user, clues.ToCore(err))
|
|
||||||
si.userID = ptr.Val(user.GetId())
|
|
||||||
|
|
||||||
secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.secondaryUser, clues.ToCore(err))
|
|
||||||
si.secondaryUserID = ptr.Val(secondaryUser.GetId())
|
|
||||||
|
|
||||||
tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser, api.CallConfig{})
|
|
||||||
require.NoError(t, err, "fetching user", si.tertiaryUser, clues.ToCore(err))
|
|
||||||
si.tertiaryUserID = ptr.Val(tertiaryUser.GetId())
|
|
||||||
|
|
||||||
suite.suiteInfo = si
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveNightlySuite) TestRestoreAndBackup_MultipleFilesAndFolders_NoPermissions() {
|
func (suite *OneDriveNightlySuite) TestRestoreAndBackup_MultipleFilesAndFolders_NoPermissions() {
|
||||||
testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(suite, 0)
|
testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(suite, suite.m365, suite.resourceAndSvc, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveNightlySuite) TestPermissionsRestoreAndBackup() {
|
func (suite *OneDriveNightlySuite) TestPermissionsRestoreAndBackup() {
|
||||||
testPermissionsRestoreAndBackup(suite, version.OneDrive1DataAndMetaFiles)
|
testPermissionsRestoreAndBackup(suite, suite.m365, suite.resourceAndSvc, version.OneDrive1DataAndMetaFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveNightlySuite) TestRestoreNoPermissionsAndBackup() {
|
func (suite *OneDriveNightlySuite) TestRestoreNoPermissionsAndBackup() {
|
||||||
testRestoreNoPermissionsAndBackup(suite, version.OneDrive1DataAndMetaFiles)
|
testRestoreNoPermissionsAndBackup(suite, suite.m365, suite.resourceAndSvc, version.OneDrive1DataAndMetaFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveNightlySuite) TestPermissionsInheritanceRestoreAndBackup() {
|
func (suite *OneDriveNightlySuite) TestPermissionsInheritanceRestoreAndBackup() {
|
||||||
// No reason why it couldn't work with previous versions, but this is when it got introduced.
|
// No reason why it couldn't work with previous versions, but this is when it got introduced.
|
||||||
testPermissionsInheritanceRestoreAndBackup(suite, version.OneDrive4DirIncludesPermissions)
|
testPermissionsInheritanceRestoreAndBackup(
|
||||||
|
suite,
|
||||||
|
suite.m365,
|
||||||
|
suite.resourceAndSvc,
|
||||||
|
version.OneDrive4DirIncludesPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveNightlySuite) TestLinkSharesInheritanceRestoreAndBackup() {
|
func (suite *OneDriveNightlySuite) TestLinkSharesInheritanceRestoreAndBackup() {
|
||||||
testLinkSharesInheritanceRestoreAndBackup(suite, version.Backup)
|
testLinkSharesInheritanceRestoreAndBackup(suite, suite.m365, suite.resourceAndSvc, version.Backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveNightlySuite) TestRestoreFolderNamedFolderRegression() {
|
func (suite *OneDriveNightlySuite) TestRestoreFolderNamedFolderRegression() {
|
||||||
// No reason why it couldn't work with previous versions, but this is when it got introduced.
|
// No reason why it couldn't work with previous versions, but this is when it got introduced.
|
||||||
testRestoreFolderNamedFolderRegression(suite, version.All8MigrateUserPNToID)
|
testRestoreFolderNamedFolderRegression(suite, suite.m365, suite.resourceAndSvc, version.All8MigrateUserPNToID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
|
func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
|
||||||
suite oneDriveSuite,
|
suite tester.Suite,
|
||||||
|
m365 its.M365IntgTestSetup,
|
||||||
|
resourceAndSvc its.ResourceServicer,
|
||||||
startVersion int,
|
startVersion int,
|
||||||
) {
|
) {
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
// Get the default drive ID for the test user.
|
// Get the default drive ID for the test user.
|
||||||
driveID := mustGetDefaultDriveID(
|
driveID := resourceAndSvc.Resource().DriveID
|
||||||
t,
|
|
||||||
ctx,
|
|
||||||
suite.APIClient(),
|
|
||||||
suite.Service(),
|
|
||||||
suite.ResourceOwner())
|
|
||||||
|
|
||||||
rootPath := []string{
|
rootPath := []string{
|
||||||
odConsts.DrivesPathDir,
|
odConsts.DrivesPathDir,
|
||||||
@ -488,17 +315,17 @@ func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err := stub.DataForInfo(suite.Service(), cols, version.Backup)
|
expected, err := stub.DataForInfo(resourceAndSvc.Service(), cols, version.Backup)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
for vn := startVersion; vn <= version.Backup; vn++ {
|
for vn := startVersion; vn <= version.Backup; vn++ {
|
||||||
suite.Run(fmt.Sprintf("Version%d", vn), func() {
|
suite.Run(fmt.Sprintf("Version%d", vn), func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
input, err := stub.DataForInfo(suite.Service(), cols, vn)
|
input, err := stub.DataForInfo(resourceAndSvc.Service(), cols, vn)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
testData := restoreBackupInfoMultiVersion{
|
testData := restoreBackupInfoMultiVersion{
|
||||||
service: suite.Service(),
|
service: resourceAndSvc.Service(),
|
||||||
backupVersion: vn,
|
backupVersion: vn,
|
||||||
collectionsPrevious: input,
|
collectionsPrevious: input,
|
||||||
collectionsLatest: expected,
|
collectionsLatest: expected,
|
||||||
@ -511,8 +338,8 @@ func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
|
|||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
|
|
||||||
cfg := m365Stub.ConfigInfo{
|
cfg := m365Stub.ConfigInfo{
|
||||||
Tenant: suite.Tenant(),
|
Tenant: m365.TenantID,
|
||||||
ResourceOwners: []string{suite.ResourceOwner()},
|
ResourceOwners: []string{resourceAndSvc.Resource().ID},
|
||||||
Service: testData.service,
|
Service: testData.service,
|
||||||
Opts: opts,
|
Opts: opts,
|
||||||
RestoreCfg: restoreCfg,
|
RestoreCfg: restoreCfg,
|
||||||
@ -523,21 +350,14 @@ func testRestoreAndBackupMultipleFilesAndFoldersNoPermissions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
func testPermissionsRestoreAndBackup(
|
||||||
t := suite.T()
|
suite tester.Suite,
|
||||||
|
m365 its.M365IntgTestSetup,
|
||||||
ctx, flush := tester.NewContext(t)
|
resourceAndSvc its.ResourceServicer,
|
||||||
defer flush()
|
startVersion int,
|
||||||
|
) {
|
||||||
secondaryUserName, secondaryUserID := suite.SecondaryUser()
|
|
||||||
|
|
||||||
// Get the default drive ID for the test user.
|
// Get the default drive ID for the test user.
|
||||||
driveID := mustGetDefaultDriveID(
|
driveID := resourceAndSvc.Resource().DriveID
|
||||||
t,
|
|
||||||
ctx,
|
|
||||||
suite.APIClient(),
|
|
||||||
suite.Service(),
|
|
||||||
suite.ResourceOwner())
|
|
||||||
|
|
||||||
fileName2 := "test-file2.txt"
|
fileName2 := "test-file2.txt"
|
||||||
folderCName := "folder-c"
|
folderCName := "folder-c"
|
||||||
@ -587,8 +407,8 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
Data: fileAData,
|
Data: fileAData,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: writePerm,
|
Roles: writePerm,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -614,8 +434,8 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
Name: folderAName,
|
Name: folderAName,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: readPerm,
|
Roles: readPerm,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -624,8 +444,8 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
Name: folderCName,
|
Name: folderCName,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: readPerm,
|
Roles: readPerm,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -645,8 +465,8 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
Data: fileBData,
|
Data: fileBData,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: writePerm,
|
Roles: writePerm,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -657,8 +477,8 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
Name: folderAName,
|
Name: folderAName,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: readPerm,
|
Roles: readPerm,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -676,15 +496,15 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
// name: fileName,
|
// name: fileName,
|
||||||
// data: fileDData,
|
// data: fileDData,
|
||||||
// perms: stub.PermData{
|
// perms: stub.PermData{
|
||||||
// user: secondaryUserName,
|
// user: m365.SecondaryUser.Email,
|
||||||
// entityID: secondaryUserID,
|
// entityID: m365.SecondaryUser.ID,
|
||||||
// roles: readPerm,
|
// roles: readPerm,
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// Perms: stub.PermData{
|
// Perms: stub.PermData{
|
||||||
// User: secondaryUserName,
|
// User: m365.SecondaryUser.Email,
|
||||||
// EntityID: secondaryUserID,
|
// EntityID: m365.SecondaryUser.ID,
|
||||||
// Roles: readPerm,
|
// Roles: readPerm,
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
@ -698,8 +518,8 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
Data: fileEData,
|
Data: fileEData,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: writePerm,
|
Roles: writePerm,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -707,8 +527,8 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
},
|
},
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: readPerm,
|
Roles: readPerm,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -728,17 +548,18 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
},
|
},
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: readPerm,
|
Roles: readPerm,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err := stub.DataForInfo(suite.Service(), cols, version.Backup)
|
expected, err := stub.DataForInfo(resourceAndSvc.Service(), cols, version.Backup)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
bss := suite.Service().String()
|
|
||||||
|
bss := resourceAndSvc.Service().String()
|
||||||
|
|
||||||
for vn := startVersion; vn <= version.Backup; vn++ {
|
for vn := startVersion; vn <= version.Backup; vn++ {
|
||||||
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
||||||
@ -746,11 +567,11 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
// Ideally this can always be true or false and still
|
// Ideally this can always be true or false and still
|
||||||
// work, but limiting older versions to use emails so as
|
// work, but limiting older versions to use emails so as
|
||||||
// to validate that flow as well.
|
// to validate that flow as well.
|
||||||
input, err := stub.DataForInfo(suite.Service(), cols, vn)
|
input, err := stub.DataForInfo(resourceAndSvc.Service(), cols, vn)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
testData := restoreBackupInfoMultiVersion{
|
testData := restoreBackupInfoMultiVersion{
|
||||||
service: suite.Service(),
|
service: resourceAndSvc.Service(),
|
||||||
backupVersion: vn,
|
backupVersion: vn,
|
||||||
collectionsPrevious: input,
|
collectionsPrevious: input,
|
||||||
collectionsLatest: expected,
|
collectionsLatest: expected,
|
||||||
@ -763,8 +584,8 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
|
|
||||||
cfg := m365Stub.ConfigInfo{
|
cfg := m365Stub.ConfigInfo{
|
||||||
Tenant: suite.Tenant(),
|
Tenant: m365.TenantID,
|
||||||
ResourceOwners: []string{suite.ResourceOwner()},
|
ResourceOwners: []string{resourceAndSvc.Resource().ID},
|
||||||
Service: testData.service,
|
Service: testData.service,
|
||||||
Opts: opts,
|
Opts: opts,
|
||||||
RestoreCfg: restoreCfg,
|
RestoreCfg: restoreCfg,
|
||||||
@ -775,21 +596,14 @@ func testPermissionsRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRestoreNoPermissionsAndBackup(suite oneDriveSuite, startVersion int) {
|
func testRestoreNoPermissionsAndBackup(
|
||||||
t := suite.T()
|
suite tester.Suite,
|
||||||
|
m365 its.M365IntgTestSetup,
|
||||||
ctx, flush := tester.NewContext(t)
|
resourceAndSvc its.ResourceServicer,
|
||||||
defer flush()
|
startVersion int,
|
||||||
|
) {
|
||||||
secondaryUserName, secondaryUserID := suite.SecondaryUser()
|
|
||||||
|
|
||||||
// Get the default drive ID for the test user.
|
// Get the default drive ID for the test user.
|
||||||
driveID := mustGetDefaultDriveID(
|
driveID := resourceAndSvc.Resource().DriveID
|
||||||
t,
|
|
||||||
ctx,
|
|
||||||
suite.APIClient(),
|
|
||||||
suite.Service(),
|
|
||||||
suite.ResourceOwner())
|
|
||||||
|
|
||||||
inputCols := []stub.ColInfo{
|
inputCols := []stub.ColInfo{
|
||||||
{
|
{
|
||||||
@ -804,8 +618,8 @@ func testRestoreNoPermissionsAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
Data: fileAData,
|
Data: fileAData,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: writePerm,
|
Roles: writePerm,
|
||||||
},
|
},
|
||||||
SharingMode: metadata.SharingModeCustom,
|
SharingMode: metadata.SharingModeCustom,
|
||||||
@ -832,18 +646,20 @@ func testRestoreNoPermissionsAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err := stub.DataForInfo(suite.Service(), expectedCols, version.Backup)
|
expected, err := stub.DataForInfo(resourceAndSvc.Service(), expectedCols, version.Backup)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err, clues.ToCore(err))
|
||||||
bss := suite.Service().String()
|
|
||||||
|
bss := resourceAndSvc.Service().String()
|
||||||
|
|
||||||
for vn := startVersion; vn <= version.Backup; vn++ {
|
for vn := startVersion; vn <= version.Backup; vn++ {
|
||||||
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
input, err := stub.DataForInfo(suite.Service(), inputCols, vn)
|
|
||||||
require.NoError(suite.T(), err)
|
input, err := stub.DataForInfo(resourceAndSvc.Service(), inputCols, vn)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
testData := restoreBackupInfoMultiVersion{
|
testData := restoreBackupInfoMultiVersion{
|
||||||
service: suite.Service(),
|
service: resourceAndSvc.Service(),
|
||||||
backupVersion: vn,
|
backupVersion: vn,
|
||||||
collectionsPrevious: input,
|
collectionsPrevious: input,
|
||||||
collectionsLatest: expected,
|
collectionsLatest: expected,
|
||||||
@ -856,8 +672,8 @@ func testRestoreNoPermissionsAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
|
|
||||||
cfg := m365Stub.ConfigInfo{
|
cfg := m365Stub.ConfigInfo{
|
||||||
Tenant: suite.Tenant(),
|
Tenant: m365.TenantID,
|
||||||
ResourceOwners: []string{suite.ResourceOwner()},
|
ResourceOwners: []string{resourceAndSvc.Resource().ID},
|
||||||
Service: testData.service,
|
Service: testData.service,
|
||||||
Opts: opts,
|
Opts: opts,
|
||||||
RestoreCfg: restoreCfg,
|
RestoreCfg: restoreCfg,
|
||||||
@ -870,22 +686,14 @@ func testRestoreNoPermissionsAndBackup(suite oneDriveSuite, startVersion int) {
|
|||||||
|
|
||||||
// This is similar to TestPermissionsRestoreAndBackup but tests purely
|
// This is similar to TestPermissionsRestoreAndBackup but tests purely
|
||||||
// for inheritance and that too only with newer versions
|
// for inheritance and that too only with newer versions
|
||||||
func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
func testPermissionsInheritanceRestoreAndBackup(
|
||||||
t := suite.T()
|
suite tester.Suite,
|
||||||
|
m365 its.M365IntgTestSetup,
|
||||||
ctx, flush := tester.NewContext(t)
|
resourceAndSvc its.ResourceServicer,
|
||||||
defer flush()
|
startVersion int,
|
||||||
|
) {
|
||||||
secondaryUserName, secondaryUserID := suite.SecondaryUser()
|
|
||||||
tertiaryUserName, tertiaryUserID := suite.TertiaryUser()
|
|
||||||
|
|
||||||
// Get the default drive ID for the test user.
|
// Get the default drive ID for the test user.
|
||||||
driveID := mustGetDefaultDriveID(
|
driveID := resourceAndSvc.Resource().DriveID
|
||||||
t,
|
|
||||||
ctx,
|
|
||||||
suite.APIClient(),
|
|
||||||
suite.Service(),
|
|
||||||
suite.ResourceOwner())
|
|
||||||
|
|
||||||
folderAName := "custom"
|
folderAName := "custom"
|
||||||
folderBName := "inherited"
|
folderBName := "inherited"
|
||||||
@ -929,8 +737,8 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
|
|||||||
Data: fileAData,
|
Data: fileAData,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: secondaryUserName,
|
User: m365.SecondaryUser.Email,
|
||||||
EntityID: secondaryUserID,
|
EntityID: m365.SecondaryUser.ID,
|
||||||
Roles: writePerm,
|
Roles: writePerm,
|
||||||
},
|
},
|
||||||
SharingMode: metadata.SharingModeCustom,
|
SharingMode: metadata.SharingModeCustom,
|
||||||
@ -1002,8 +810,8 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
|
|||||||
},
|
},
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: tertiaryUserName,
|
User: m365.TertiaryUser.Email,
|
||||||
EntityID: tertiaryUserID,
|
EntityID: m365.TertiaryUser.ID,
|
||||||
Roles: readPerm,
|
Roles: readPerm,
|
||||||
},
|
},
|
||||||
SharingMode: metadata.SharingModeCustom,
|
SharingMode: metadata.SharingModeCustom,
|
||||||
@ -1014,8 +822,8 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
|
|||||||
Files: fileSet,
|
Files: fileSet,
|
||||||
Meta: stub.MetaData{
|
Meta: stub.MetaData{
|
||||||
Perms: stub.PermData{
|
Perms: stub.PermData{
|
||||||
User: tertiaryUserName,
|
User: m365.TertiaryUser.Email,
|
||||||
EntityID: tertiaryUserID,
|
EntityID: m365.TertiaryUser.ID,
|
||||||
Roles: writePerm,
|
Roles: writePerm,
|
||||||
},
|
},
|
||||||
SharingMode: metadata.SharingModeCustom,
|
SharingMode: metadata.SharingModeCustom,
|
||||||
@ -1037,9 +845,10 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err := stub.DataForInfo(suite.Service(), cols, version.Backup)
|
expected, err := stub.DataForInfo(resourceAndSvc.Service(), cols, version.Backup)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
bss := suite.Service().String()
|
|
||||||
|
bss := resourceAndSvc.Service().String()
|
||||||
|
|
||||||
for vn := startVersion; vn <= version.Backup; vn++ {
|
for vn := startVersion; vn <= version.Backup; vn++ {
|
||||||
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
||||||
@ -1047,11 +856,11 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
|
|||||||
// Ideally this can always be true or false and still
|
// Ideally this can always be true or false and still
|
||||||
// work, but limiting older versions to use emails so as
|
// work, but limiting older versions to use emails so as
|
||||||
// to validate that flow as well.
|
// to validate that flow as well.
|
||||||
input, err := stub.DataForInfo(suite.Service(), cols, vn)
|
input, err := stub.DataForInfo(resourceAndSvc.Service(), cols, vn)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
testData := restoreBackupInfoMultiVersion{
|
testData := restoreBackupInfoMultiVersion{
|
||||||
service: suite.Service(),
|
service: resourceAndSvc.Service(),
|
||||||
backupVersion: vn,
|
backupVersion: vn,
|
||||||
collectionsPrevious: input,
|
collectionsPrevious: input,
|
||||||
collectionsLatest: expected,
|
collectionsLatest: expected,
|
||||||
@ -1064,8 +873,8 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
|
|||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
|
|
||||||
cfg := m365Stub.ConfigInfo{
|
cfg := m365Stub.ConfigInfo{
|
||||||
Tenant: suite.Tenant(),
|
Tenant: m365.TenantID,
|
||||||
ResourceOwners: []string{suite.ResourceOwner()},
|
ResourceOwners: []string{resourceAndSvc.Resource().ID},
|
||||||
Service: testData.service,
|
Service: testData.service,
|
||||||
Opts: opts,
|
Opts: opts,
|
||||||
RestoreCfg: restoreCfg,
|
RestoreCfg: restoreCfg,
|
||||||
@ -1076,33 +885,26 @@ func testPermissionsInheritanceRestoreAndBackup(suite oneDriveSuite, startVersio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLinkSharesInheritanceRestoreAndBackup(suite oneDriveSuite, startVersion int) {
|
func testLinkSharesInheritanceRestoreAndBackup(
|
||||||
t := suite.T()
|
suite tester.Suite,
|
||||||
|
m365 its.M365IntgTestSetup,
|
||||||
ctx, flush := tester.NewContext(t)
|
resourceAndSvc its.ResourceServicer,
|
||||||
defer flush()
|
startVersion int,
|
||||||
|
) {
|
||||||
secondaryUserName, secondaryUserID := suite.SecondaryUser()
|
|
||||||
secondaryUser := metadata.Entity{
|
secondaryUser := metadata.Entity{
|
||||||
ID: secondaryUserID,
|
ID: m365.SecondaryUser.ID,
|
||||||
Email: secondaryUserName,
|
Email: m365.SecondaryUser.Email,
|
||||||
EntityType: metadata.GV2User,
|
EntityType: metadata.GV2User,
|
||||||
}
|
}
|
||||||
|
|
||||||
tertiaryUserName, tertiaryUserID := suite.TertiaryUser()
|
|
||||||
tertiaryUser := metadata.Entity{
|
tertiaryUser := metadata.Entity{
|
||||||
ID: tertiaryUserID,
|
ID: m365.TertiaryUser.ID,
|
||||||
Email: tertiaryUserName,
|
Email: m365.TertiaryUser.Email,
|
||||||
EntityType: metadata.GV2User,
|
EntityType: metadata.GV2User,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the default drive ID for the test user.
|
// Get the default drive ID for the test user.
|
||||||
driveID := mustGetDefaultDriveID(
|
driveID := resourceAndSvc.Resource().DriveID
|
||||||
t,
|
|
||||||
ctx,
|
|
||||||
suite.APIClient(),
|
|
||||||
suite.Service(),
|
|
||||||
suite.ResourceOwner())
|
|
||||||
|
|
||||||
folderAName := "custom"
|
folderAName := "custom"
|
||||||
folderBName := "inherited"
|
folderBName := "inherited"
|
||||||
@ -1246,9 +1048,10 @@ func testLinkSharesInheritanceRestoreAndBackup(suite oneDriveSuite, startVersion
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err := stub.DataForInfo(suite.Service(), cols, version.Backup)
|
expected, err := stub.DataForInfo(resourceAndSvc.Service(), cols, version.Backup)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
bss := suite.Service().String()
|
|
||||||
|
bss := resourceAndSvc.Service().String()
|
||||||
|
|
||||||
for vn := startVersion; vn <= version.Backup; vn++ {
|
for vn := startVersion; vn <= version.Backup; vn++ {
|
||||||
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
||||||
@ -1256,11 +1059,11 @@ func testLinkSharesInheritanceRestoreAndBackup(suite oneDriveSuite, startVersion
|
|||||||
// Ideally this can always be true or false and still
|
// Ideally this can always be true or false and still
|
||||||
// work, but limiting older versions to use emails so as
|
// work, but limiting older versions to use emails so as
|
||||||
// to validate that flow as well.
|
// to validate that flow as well.
|
||||||
input, err := stub.DataForInfo(suite.Service(), cols, vn)
|
input, err := stub.DataForInfo(resourceAndSvc.Service(), cols, vn)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
testData := restoreBackupInfoMultiVersion{
|
testData := restoreBackupInfoMultiVersion{
|
||||||
service: suite.Service(),
|
service: resourceAndSvc.Service(),
|
||||||
backupVersion: vn,
|
backupVersion: vn,
|
||||||
collectionsPrevious: input,
|
collectionsPrevious: input,
|
||||||
collectionsLatest: expected,
|
collectionsLatest: expected,
|
||||||
@ -1273,8 +1076,8 @@ func testLinkSharesInheritanceRestoreAndBackup(suite oneDriveSuite, startVersion
|
|||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
|
|
||||||
cfg := m365Stub.ConfigInfo{
|
cfg := m365Stub.ConfigInfo{
|
||||||
Tenant: suite.Tenant(),
|
Tenant: m365.TenantID,
|
||||||
ResourceOwners: []string{suite.ResourceOwner()},
|
ResourceOwners: []string{resourceAndSvc.Resource().ID},
|
||||||
Service: testData.service,
|
Service: testData.service,
|
||||||
Opts: opts,
|
Opts: opts,
|
||||||
RestoreCfg: restoreCfg,
|
RestoreCfg: restoreCfg,
|
||||||
@ -1286,21 +1089,13 @@ func testLinkSharesInheritanceRestoreAndBackup(suite oneDriveSuite, startVersion
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testRestoreFolderNamedFolderRegression(
|
func testRestoreFolderNamedFolderRegression(
|
||||||
suite oneDriveSuite,
|
suite tester.Suite,
|
||||||
|
m365 its.M365IntgTestSetup,
|
||||||
|
resourceAndSvc its.ResourceServicer,
|
||||||
startVersion int,
|
startVersion int,
|
||||||
) {
|
) {
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
// Get the default drive ID for the test user.
|
// Get the default drive ID for the test user.
|
||||||
driveID := mustGetDefaultDriveID(
|
driveID := resourceAndSvc.Resource().DriveID
|
||||||
suite.T(),
|
|
||||||
ctx,
|
|
||||||
suite.APIClient(),
|
|
||||||
suite.Service(),
|
|
||||||
suite.ResourceOwner())
|
|
||||||
|
|
||||||
rootPath := []string{
|
rootPath := []string{
|
||||||
odConsts.DrivesPathDir,
|
odConsts.DrivesPathDir,
|
||||||
@ -1369,18 +1164,19 @@ func testRestoreFolderNamedFolderRegression(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err := stub.DataForInfo(suite.Service(), cols, version.Backup)
|
expected, err := stub.DataForInfo(resourceAndSvc.Service(), cols, version.Backup)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
bss := suite.Service().String()
|
|
||||||
|
bss := resourceAndSvc.Service().String()
|
||||||
|
|
||||||
for vn := startVersion; vn <= version.Backup; vn++ {
|
for vn := startVersion; vn <= version.Backup; vn++ {
|
||||||
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
suite.Run(fmt.Sprintf("%s-Version%d", bss, vn), func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
input, err := stub.DataForInfo(suite.Service(), cols, vn)
|
input, err := stub.DataForInfo(resourceAndSvc.Service(), cols, vn)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
testData := restoreBackupInfoMultiVersion{
|
testData := restoreBackupInfoMultiVersion{
|
||||||
service: suite.Service(),
|
service: resourceAndSvc.Service(),
|
||||||
backupVersion: vn,
|
backupVersion: vn,
|
||||||
collectionsPrevious: input,
|
collectionsPrevious: input,
|
||||||
collectionsLatest: expected,
|
collectionsLatest: expected,
|
||||||
@ -1392,8 +1188,8 @@ func testRestoreFolderNamedFolderRegression(
|
|||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
|
|
||||||
cfg := m365Stub.ConfigInfo{
|
cfg := m365Stub.ConfigInfo{
|
||||||
Tenant: suite.Tenant(),
|
Tenant: m365.TenantID,
|
||||||
ResourceOwners: []string{suite.ResourceOwner()},
|
ResourceOwners: []string{resourceAndSvc.Resource().ID},
|
||||||
Service: testData.service,
|
Service: testData.service,
|
||||||
Opts: opts,
|
Opts: opts,
|
||||||
RestoreCfg: restoreCfg,
|
RestoreCfg: restoreCfg,
|
||||||
|
|||||||
@ -9,14 +9,12 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/data/mock"
|
"github.com/alcionai/corso/src/internal/data/mock"
|
||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -64,9 +62,7 @@ func (suite *GroupsUnitSuite) TestConsumeRestoreCollections_noErrorOnGroups() {
|
|||||||
|
|
||||||
type groupsIntegrationSuite struct {
|
type groupsIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
resource string
|
m365 its.M365IntgTestSetup
|
||||||
tenantID string
|
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupsIntegrationSuite(t *testing.T) {
|
func TestGroupsIntegrationSuite(t *testing.T) {
|
||||||
@ -79,25 +75,12 @@ func TestGroupsIntegrationSuite(t *testing.T) {
|
|||||||
|
|
||||||
func (suite *groupsIntegrationSuite) SetupSuite() {
|
func (suite *groupsIntegrationSuite) SetupSuite() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
||||||
|
|
||||||
suite.resource = tconfig.M365TeamID(t)
|
|
||||||
|
|
||||||
acct := tconfig.NewM365Account(t)
|
|
||||||
creds, err := acct.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.tenantID = creds.AzureTenantID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test for getSiteName
|
// test for getSiteName
|
||||||
@ -107,12 +90,9 @@ func (suite *groupsIntegrationSuite) TestGetSiteName() {
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
rootSite, err := suite.ac.Groups().GetRootSite(ctx, suite.resource)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
// Generate a fake site ID that appears valid to graph API but doesn't actually exist.
|
// Generate a fake site ID that appears valid to graph API but doesn't actually exist.
|
||||||
// This "could" be flaky, but highly unlikely
|
// This "could" be flaky, but highly unlikely
|
||||||
unavailableSiteID := []rune(ptr.Val(rootSite.GetId()))
|
unavailableSiteID := []rune(suite.m365.Group.RootSite.ID)
|
||||||
firstIDChar := slices.Index(unavailableSiteID, ',') + 1
|
firstIDChar := slices.Index(unavailableSiteID, ',') + 1
|
||||||
|
|
||||||
if unavailableSiteID[firstIDChar] != '2' {
|
if unavailableSiteID[firstIDChar] != '2' {
|
||||||
@ -131,9 +111,9 @@ func (suite *groupsIntegrationSuite) TestGetSiteName() {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid",
|
name: "valid",
|
||||||
siteID: ptr.Val(rootSite.GetId()),
|
siteID: suite.m365.Group.RootSite.ID,
|
||||||
webURL: ptr.Val(rootSite.GetWebUrl()),
|
webURL: suite.m365.Group.RootSite.WebURL,
|
||||||
siteName: *rootSite.GetDisplayName(),
|
siteName: suite.m365.Group.RootSite.DisplayName,
|
||||||
webURLToSiteNames: map[string]string{},
|
webURLToSiteNames: map[string]string{},
|
||||||
expectErr: assert.NoError,
|
expectErr: assert.NoError,
|
||||||
},
|
},
|
||||||
@ -163,7 +143,7 @@ func (suite *groupsIntegrationSuite) TestGetSiteName() {
|
|||||||
ctx,
|
ctx,
|
||||||
test.siteID,
|
test.siteID,
|
||||||
test.webURL,
|
test.webURL,
|
||||||
suite.ac.Sites(),
|
suite.m365.AC.Sites(),
|
||||||
test.webURLToSiteNames)
|
test.webURLToSiteNames)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
|||||||
@ -197,7 +197,12 @@ func (h BackupHandler[T]) AugmentItemInfo(
|
|||||||
return h.ItemInfo
|
return h.ItemInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BackupHandler[T]) Get(context.Context, string, map[string]string) (*http.Response, error) {
|
func (h *BackupHandler[T]) Get(
|
||||||
|
context.Context,
|
||||||
|
string,
|
||||||
|
map[string]string,
|
||||||
|
bool,
|
||||||
|
) (*http.Response, error) {
|
||||||
c := h.getCall
|
c := h.getCall
|
||||||
h.getCall++
|
h.getCall++
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/streamstore"
|
"github.com/alcionai/corso/src/internal/streamstore"
|
||||||
ssmock "github.com/alcionai/corso/src/internal/streamstore/mock"
|
ssmock "github.com/alcionai/corso/src/internal/streamstore/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
@ -47,7 +48,6 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
selTD "github.com/alcionai/corso/src/pkg/selectors/testdata"
|
selTD "github.com/alcionai/corso/src/pkg/selectors/testdata"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
storeTD "github.com/alcionai/corso/src/pkg/storage/testdata"
|
storeTD "github.com/alcionai/corso/src/pkg/storage/testdata"
|
||||||
"github.com/alcionai/corso/src/pkg/store"
|
"github.com/alcionai/corso/src/pkg/store"
|
||||||
@ -420,6 +420,9 @@ func (suite *BackupOpUnitSuite) TestNewBackupOperation_configuredOptionsMatchInp
|
|||||||
MaxPages: 46,
|
MaxPages: 46,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
|
SkipEventsOnInstance503ForResources: map[string]struct{}{
|
||||||
|
"resource": {},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
@ -1506,8 +1509,7 @@ func withoutModified(de details.Entry) details.Entry {
|
|||||||
|
|
||||||
type BackupOpIntegrationSuite struct {
|
type BackupOpIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
user, site string
|
m365 its.M365IntgTestSetup
|
||||||
ac api.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackupOpIntegrationSuite(t *testing.T) {
|
func TestBackupOpIntegrationSuite(t *testing.T) {
|
||||||
@ -1520,25 +1522,12 @@ func TestBackupOpIntegrationSuite(t *testing.T) {
|
|||||||
|
|
||||||
func (suite *BackupOpIntegrationSuite) SetupSuite() {
|
func (suite *BackupOpIntegrationSuite) SetupSuite() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
suite.m365 = its.GetM365(t)
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
||||||
|
|
||||||
suite.user = tconfig.M365UserID(t)
|
|
||||||
suite.site = tconfig.M365SiteID(t)
|
|
||||||
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
|
|
||||||
creds, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
suite.ac, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
count.New())
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
||||||
@ -1546,8 +1535,8 @@ func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
|||||||
kw = &kopia.Wrapper{}
|
kw = &kopia.Wrapper{}
|
||||||
sw = store.NewWrapper(&kopia.ModelStore{})
|
sw = store.NewWrapper(&kopia.ModelStore{})
|
||||||
ctrl = &mock.Controller{}
|
ctrl = &mock.Controller{}
|
||||||
acct = tconfig.NewM365Account(suite.T())
|
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
|
acct = suite.m365.Acct
|
||||||
)
|
)
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
@ -1745,7 +1734,7 @@ func makeMockItem(
|
|||||||
func (suite *AssistBackupIntegrationSuite) TestBackupTypesForFailureModes() {
|
func (suite *AssistBackupIntegrationSuite) TestBackupTypesForFailureModes() {
|
||||||
var (
|
var (
|
||||||
acct = tconfig.NewM365Account(suite.T())
|
acct = tconfig.NewM365Account(suite.T())
|
||||||
tenantID = acct.Config[account.AzureTenantIDKey]
|
tenantID = acct.ID()
|
||||||
osel = selectors.NewOneDriveBackup([]string{userID})
|
osel = selectors.NewOneDriveBackup([]string{userID})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2074,7 +2063,7 @@ func selectFilesFromDeets(d details.Details) map[string]details.Entry {
|
|||||||
func (suite *AssistBackupIntegrationSuite) TestExtensionsIncrementals() {
|
func (suite *AssistBackupIntegrationSuite) TestExtensionsIncrementals() {
|
||||||
var (
|
var (
|
||||||
acct = tconfig.NewM365Account(suite.T())
|
acct = tconfig.NewM365Account(suite.T())
|
||||||
tenantID = acct.Config[account.AzureTenantIDKey]
|
tenantID = acct.ID()
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
osel = selectors.NewOneDriveBackup([]string{userID})
|
osel = selectors.NewOneDriveBackup([]string{userID})
|
||||||
// Default policy used by SDK clients
|
// Default policy used by SDK clients
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
"github.com/alcionai/corso/src/internal/m365/collection/drive/metadata"
|
||||||
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
@ -304,6 +305,10 @@ func RunIncrementalDriveishBackupTest(
|
|||||||
itemsRead int
|
itemsRead int
|
||||||
itemsWritten int
|
itemsWritten int
|
||||||
nonMetaItemsWritten int
|
nonMetaItemsWritten int
|
||||||
|
|
||||||
|
// TODO: Temporary mechanism to skip permissions
|
||||||
|
// related tests. Remove once we figure out the issue.
|
||||||
|
skipChecks bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "clean incremental, no changes",
|
name: "clean incremental, no changes",
|
||||||
@ -352,6 +357,7 @@ func RunIncrementalDriveishBackupTest(
|
|||||||
itemsRead: 1, // .data file for newitem
|
itemsRead: 1, // .data file for newitem
|
||||||
itemsWritten: 3, // .meta for newitem, .dirmeta for parent (.data is not written as it is not updated)
|
itemsWritten: 3, // .meta for newitem, .dirmeta for parent (.data is not written as it is not updated)
|
||||||
nonMetaItemsWritten: 0, // none because the file is considered cached instead of written.
|
nonMetaItemsWritten: 0, // none because the file is considered cached instead of written.
|
||||||
|
skipChecks: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "remove permission from new file",
|
name: "remove permission from new file",
|
||||||
@ -371,6 +377,7 @@ func RunIncrementalDriveishBackupTest(
|
|||||||
itemsRead: 1, // .data file for newitem
|
itemsRead: 1, // .data file for newitem
|
||||||
itemsWritten: 3, // .meta for newitem, .dirmeta for parent (.data is not written as it is not updated)
|
itemsWritten: 3, // .meta for newitem, .dirmeta for parent (.data is not written as it is not updated)
|
||||||
nonMetaItemsWritten: 0, // none because the file is considered cached instead of written.
|
nonMetaItemsWritten: 0, // none because the file is considered cached instead of written.
|
||||||
|
skipChecks: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add permission to container",
|
name: "add permission to container",
|
||||||
@ -391,6 +398,7 @@ func RunIncrementalDriveishBackupTest(
|
|||||||
itemsRead: 0,
|
itemsRead: 0,
|
||||||
itemsWritten: 2, // .dirmeta for collection
|
itemsWritten: 2, // .dirmeta for collection
|
||||||
nonMetaItemsWritten: 0, // no files updated as update on container
|
nonMetaItemsWritten: 0, // no files updated as update on container
|
||||||
|
skipChecks: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "remove permission from container",
|
name: "remove permission from container",
|
||||||
@ -411,6 +419,7 @@ func RunIncrementalDriveishBackupTest(
|
|||||||
itemsRead: 0,
|
itemsRead: 0,
|
||||||
itemsWritten: 2, // .dirmeta for collection
|
itemsWritten: 2, // .dirmeta for collection
|
||||||
nonMetaItemsWritten: 0, // no files updated
|
nonMetaItemsWritten: 0, // no files updated
|
||||||
|
skipChecks: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "update contents of a file",
|
name: "update contents of a file",
|
||||||
@ -740,9 +749,11 @@ func RunIncrementalDriveishBackupTest(
|
|||||||
assertReadWrite = assert.LessOrEqual
|
assertReadWrite = assert.LessOrEqual
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !test.skipChecks {
|
||||||
assertReadWrite(t, expectWrites, incBO.Results.ItemsWritten, "incremental items written")
|
assertReadWrite(t, expectWrites, incBO.Results.ItemsWritten, "incremental items written")
|
||||||
assertReadWrite(t, expectNonMetaWrites, incBO.Results.NonMetaItemsWritten, "incremental non-meta items written")
|
assertReadWrite(t, expectNonMetaWrites, incBO.Results.NonMetaItemsWritten, "incremental non-meta items written")
|
||||||
assertReadWrite(t, expectReads, incBO.Results.ItemsRead, "incremental items read")
|
assertReadWrite(t, expectReads, incBO.Results.ItemsRead, "incremental items read")
|
||||||
|
}
|
||||||
|
|
||||||
assert.NoError(t, incBO.Errors.Failure(), "incremental non-recoverable error", clues.ToCore(incBO.Errors.Failure()))
|
assert.NoError(t, incBO.Errors.Failure(), "incremental non-recoverable error", clues.ToCore(incBO.Errors.Failure()))
|
||||||
assert.Empty(t, incBO.Errors.Recovered(), "incremental recoverable/iteration errors")
|
assert.Empty(t, incBO.Errors.Recovered(), "incremental recoverable/iteration errors")
|
||||||
@ -988,7 +999,7 @@ func RunDriveRestoreToAlternateProtectedResource(
|
|||||||
suite tester.Suite,
|
suite tester.Suite,
|
||||||
ac api.Client,
|
ac api.Client,
|
||||||
sel selectors.Selector, // owner should match 'from', both Restore and Backup types work.
|
sel selectors.Selector, // owner should match 'from', both Restore and Backup types work.
|
||||||
driveFrom, driveTo IDs,
|
driveFrom, driveTo its.IDs,
|
||||||
toResource string,
|
toResource string,
|
||||||
) {
|
) {
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
exchTD "github.com/alcionai/corso/src/internal/m365/service/exchange/testdata"
|
exchTD "github.com/alcionai/corso/src/internal/m365/service/exchange/testdata"
|
||||||
. "github.com/alcionai/corso/src/internal/operations/test/m365"
|
. "github.com/alcionai/corso/src/internal/operations/test/m365"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
@ -41,7 +42,7 @@ import (
|
|||||||
|
|
||||||
type ExchangeBackupIntgSuite struct {
|
type ExchangeBackupIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExchangeBackupIntgSuite(t *testing.T) {
|
func TestExchangeBackupIntgSuite(t *testing.T) {
|
||||||
@ -53,7 +54,7 @@ func TestExchangeBackupIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeBackupIntgSuite) SetupSuite() {
|
func (suite *ExchangeBackupIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetadataFileNames produces the category-specific set of filenames used to
|
// MetadataFileNames produces the category-specific set of filenames used to
|
||||||
@ -80,9 +81,9 @@ func (suite *ExchangeBackupIntgSuite) TestBackup_Run_exchange() {
|
|||||||
{
|
{
|
||||||
name: "Mail",
|
name: "Mail",
|
||||||
selector: func() *selectors.ExchangeBackup {
|
selector: func() *selectors.ExchangeBackup {
|
||||||
sel := selectors.NewExchangeBackup([]string{suite.its.User.ID})
|
sel := selectors.NewExchangeBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(sel.MailFolders([]string{api.MailInbox}, selectors.PrefixMatch()))
|
sel.Include(sel.MailFolders([]string{api.MailInbox}, selectors.PrefixMatch()))
|
||||||
sel.DiscreteOwner = suite.its.User.ID
|
sel.DiscreteOwner = suite.m365.User.ID
|
||||||
|
|
||||||
return sel
|
return sel
|
||||||
},
|
},
|
||||||
@ -92,7 +93,7 @@ func (suite *ExchangeBackupIntgSuite) TestBackup_Run_exchange() {
|
|||||||
{
|
{
|
||||||
name: "Contacts",
|
name: "Contacts",
|
||||||
selector: func() *selectors.ExchangeBackup {
|
selector: func() *selectors.ExchangeBackup {
|
||||||
sel := selectors.NewExchangeBackup([]string{suite.its.User.ID})
|
sel := selectors.NewExchangeBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(sel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()))
|
sel.Include(sel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()))
|
||||||
return sel
|
return sel
|
||||||
},
|
},
|
||||||
@ -102,7 +103,7 @@ func (suite *ExchangeBackupIntgSuite) TestBackup_Run_exchange() {
|
|||||||
{
|
{
|
||||||
name: "Calendar Events",
|
name: "Calendar Events",
|
||||||
selector: func() *selectors.ExchangeBackup {
|
selector: func() *selectors.ExchangeBackup {
|
||||||
sel := selectors.NewExchangeBackup([]string{suite.its.User.ID})
|
sel := selectors.NewExchangeBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(sel.EventCalendars([]string{api.DefaultCalendar}, selectors.PrefixMatch()))
|
sel.Include(sel.EventCalendars([]string{api.DefaultCalendar}, selectors.PrefixMatch()))
|
||||||
return sel
|
return sel
|
||||||
},
|
},
|
||||||
@ -270,7 +271,7 @@ func testExchangeContinuousBackups(suite *ExchangeBackupIntgSuite, toggles contr
|
|||||||
// later on during the tests. Putting their identifiers into the selector
|
// later on during the tests. Putting their identifiers into the selector
|
||||||
// at this point is harmless.
|
// at this point is harmless.
|
||||||
containers = []string{container1, container2, container3, containerRename}
|
containers = []string{container1, container2, container3, containerRename}
|
||||||
sel = selectors.NewExchangeBackup([]string{suite.its.User.ID})
|
sel = selectors.NewExchangeBackup([]string{suite.m365.User.ID})
|
||||||
whatSet = deeTD.CategoryFromRepoRef
|
whatSet = deeTD.CategoryFromRepoRef
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
)
|
)
|
||||||
@ -310,7 +311,7 @@ func testExchangeContinuousBackups(suite *ExchangeBackupIntgSuite, toggles contr
|
|||||||
|
|
||||||
mailDBF := func(id, timeStamp, subject, body string) []byte {
|
mailDBF := func(id, timeStamp, subject, body string) []byte {
|
||||||
return exchMock.MessageWith(
|
return exchMock.MessageWith(
|
||||||
suite.its.User.ID, suite.its.User.ID, suite.its.User.ID,
|
suite.m365.User.ID, suite.m365.User.ID, suite.m365.User.ID,
|
||||||
subject, body, body,
|
subject, body, body,
|
||||||
now, now, now, now)
|
now, now, now, now)
|
||||||
}
|
}
|
||||||
@ -327,7 +328,7 @@ func testExchangeContinuousBackups(suite *ExchangeBackupIntgSuite, toggles contr
|
|||||||
|
|
||||||
eventDBF := func(id, timeStamp, subject, body string) []byte {
|
eventDBF := func(id, timeStamp, subject, body string) []byte {
|
||||||
return exchMock.EventWith(
|
return exchMock.EventWith(
|
||||||
suite.its.User.ID, subject, body, body,
|
suite.m365.User.ID, subject, body, body,
|
||||||
exchMock.NoOriginalStartDate, now, now,
|
exchMock.NoOriginalStartDate, now, now,
|
||||||
exchMock.NoRecurrence, exchMock.NoAttendees,
|
exchMock.NoRecurrence, exchMock.NoAttendees,
|
||||||
exchMock.NoAttachments, exchMock.NoCancelledOccurrences,
|
exchMock.NoAttachments, exchMock.NoCancelledOccurrences,
|
||||||
@ -596,7 +597,7 @@ func testExchangeContinuousBackups(suite *ExchangeBackupIntgSuite, toggles contr
|
|||||||
service,
|
service,
|
||||||
category,
|
category,
|
||||||
selectors.NewExchangeRestore([]string{uidn.ID()}).Selector,
|
selectors.NewExchangeRestore([]string{uidn.ID()}).Selector,
|
||||||
creds.AzureTenantID, suite.its.User.ID, "", "", container3,
|
creds.AzureTenantID, suite.m365.User.ID, "", "", container3,
|
||||||
2,
|
2,
|
||||||
version.Backup,
|
version.Backup,
|
||||||
gen.dbf)
|
gen.dbf)
|
||||||
@ -889,7 +890,7 @@ func testExchangeContinuousBackups(suite *ExchangeBackupIntgSuite, toggles contr
|
|||||||
|
|
||||||
type ExchangeBackupNightlyIntgSuite struct {
|
type ExchangeBackupNightlyIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExchangeBackupNightlyIntgSuite(t *testing.T) {
|
func TestExchangeBackupNightlyIntgSuite(t *testing.T) {
|
||||||
@ -901,11 +902,11 @@ func TestExchangeBackupNightlyIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeBackupNightlyIntgSuite) SetupSuite() {
|
func (suite *ExchangeBackupNightlyIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeBackupNightlyIntgSuite) TestBackup_Run_exchangeVersion9MergeBase() {
|
func (suite *ExchangeBackupNightlyIntgSuite) TestBackup_Run_exchangeVersion9MergeBase() {
|
||||||
sel := selectors.NewExchangeBackup([]string{suite.its.User.ID})
|
sel := selectors.NewExchangeBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(
|
sel.Include(
|
||||||
sel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()),
|
sel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()),
|
||||||
// sel.EventCalendars([]string{api.DefaultCalendar}, selectors.PrefixMatch()),
|
// sel.EventCalendars([]string{api.DefaultCalendar}, selectors.PrefixMatch()),
|
||||||
@ -916,7 +917,7 @@ func (suite *ExchangeBackupNightlyIntgSuite) TestBackup_Run_exchangeVersion9Merg
|
|||||||
|
|
||||||
type ExchangeRestoreNightlyIntgSuite struct {
|
type ExchangeRestoreNightlyIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExchangeRestoreIntgSuite(t *testing.T) {
|
func TestExchangeRestoreIntgSuite(t *testing.T) {
|
||||||
@ -928,7 +929,7 @@ func TestExchangeRestoreIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ExchangeRestoreNightlyIntgSuite) SetupSuite() {
|
func (suite *ExchangeRestoreNightlyIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientItemPager interface {
|
type clientItemPager interface {
|
||||||
@ -959,7 +960,7 @@ func (suite *ExchangeRestoreNightlyIntgSuite) TestRestore_Run_exchangeWithAdvanc
|
|||||||
|
|
||||||
// a backup is required to run restores
|
// a backup is required to run restores
|
||||||
|
|
||||||
baseSel := selectors.NewExchangeBackup([]string{suite.its.User.ID})
|
baseSel := selectors.NewExchangeBackup([]string{suite.m365.User.ID})
|
||||||
baseSel.Include(
|
baseSel.Include(
|
||||||
// events cannot be run, for the same reason as incremental backups: the user needs
|
// events cannot be run, for the same reason as incremental backups: the user needs
|
||||||
// to have their account recycled.
|
// to have their account recycled.
|
||||||
@ -967,7 +968,7 @@ func (suite *ExchangeRestoreNightlyIntgSuite) TestRestore_Run_exchangeWithAdvanc
|
|||||||
baseSel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()),
|
baseSel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()),
|
||||||
baseSel.MailFolders([]string{api.MailInbox}, selectors.PrefixMatch()))
|
baseSel.MailFolders([]string{api.MailInbox}, selectors.PrefixMatch()))
|
||||||
|
|
||||||
baseSel.DiscreteOwner = suite.its.User.ID
|
baseSel.DiscreteOwner = suite.m365.User.ID
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mb = evmock.NewBus()
|
mb = evmock.NewBus()
|
||||||
@ -1002,8 +1003,8 @@ func (suite *ExchangeRestoreNightlyIntgSuite) TestRestore_Run_exchangeWithAdvanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
testCategories = map[path.CategoryType]clientItemPager{
|
testCategories = map[path.CategoryType]clientItemPager{
|
||||||
path.ContactsCategory: suite.its.AC.Contacts(),
|
path.ContactsCategory: suite.m365.AC.Contacts(),
|
||||||
path.EmailCategory: suite.its.AC.Mail(),
|
path.EmailCategory: suite.m365.AC.Mail(),
|
||||||
// path.EventsCategory: suite.its.ac.Events(),
|
// path.EventsCategory: suite.its.ac.Events(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1276,7 +1277,7 @@ func (suite *ExchangeRestoreNightlyIntgSuite) TestRestore_Run_exchangeAlternateP
|
|||||||
|
|
||||||
// a backup is required to run restores
|
// a backup is required to run restores
|
||||||
|
|
||||||
baseSel := selectors.NewExchangeBackup([]string{suite.its.User.ID})
|
baseSel := selectors.NewExchangeBackup([]string{suite.m365.User.ID})
|
||||||
baseSel.Include(
|
baseSel.Include(
|
||||||
// events cannot be run, for the same reason as incremental backups: the user needs
|
// events cannot be run, for the same reason as incremental backups: the user needs
|
||||||
// to have their account recycled.
|
// to have their account recycled.
|
||||||
@ -1284,7 +1285,7 @@ func (suite *ExchangeRestoreNightlyIntgSuite) TestRestore_Run_exchangeAlternateP
|
|||||||
baseSel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()),
|
baseSel.ContactFolders([]string{api.DefaultContacts}, selectors.PrefixMatch()),
|
||||||
baseSel.MailFolders([]string{api.MailInbox}, selectors.PrefixMatch()))
|
baseSel.MailFolders([]string{api.MailInbox}, selectors.PrefixMatch()))
|
||||||
|
|
||||||
baseSel.DiscreteOwner = suite.its.User.ID
|
baseSel.DiscreteOwner = suite.m365.User.ID
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mb = evmock.NewBus()
|
mb = evmock.NewBus()
|
||||||
@ -1303,11 +1304,11 @@ func (suite *ExchangeRestoreNightlyIntgSuite) TestRestore_Run_exchangeAlternateP
|
|||||||
var (
|
var (
|
||||||
restoreCfg = ctrlTD.DefaultRestoreConfig("exchange_restore_to_user")
|
restoreCfg = ctrlTD.DefaultRestoreConfig("exchange_restore_to_user")
|
||||||
sel = rsel.Selector
|
sel = rsel.Selector
|
||||||
userID = suite.its.User.ID
|
userID = suite.m365.User.ID
|
||||||
secondaryUserID = suite.its.SecondaryUser.ID
|
secondaryUserID = suite.m365.SecondaryUser.ID
|
||||||
uid = userID
|
uid = userID
|
||||||
acCont = suite.its.AC.Contacts()
|
acCont = suite.m365.AC.Contacts()
|
||||||
acMail = suite.its.AC.Mail()
|
acMail = suite.m365.AC.Mail()
|
||||||
// acEvts = suite.its.ac.Events()
|
// acEvts = suite.its.ac.Events()
|
||||||
firstCtr = count.New()
|
firstCtr = count.New()
|
||||||
)
|
)
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
||||||
. "github.com/alcionai/corso/src/internal/operations/test/m365"
|
. "github.com/alcionai/corso/src/internal/operations/test/m365"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
deeTD "github.com/alcionai/corso/src/pkg/backup/details/testdata"
|
deeTD "github.com/alcionai/corso/src/pkg/backup/details/testdata"
|
||||||
@ -28,7 +29,7 @@ import (
|
|||||||
|
|
||||||
type GroupsBackupIntgSuite struct {
|
type GroupsBackupIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupsBackupIntgSuite(t *testing.T) {
|
func TestGroupsBackupIntgSuite(t *testing.T) {
|
||||||
@ -40,12 +41,12 @@ func TestGroupsBackupIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupIntgSuite) SetupSuite() {
|
func (suite *GroupsBackupIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupIntgSuite) TestBackup_Run_groups() {
|
func (suite *GroupsBackupIntgSuite) TestBackup_Run_groups() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Group.ID
|
resourceID = suite.m365.Group.ID
|
||||||
sel = selectors.NewGroupsBackup([]string{resourceID})
|
sel = selectors.NewGroupsBackup([]string{resourceID})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,12 +60,12 @@ func (suite *GroupsBackupIntgSuite) TestBackup_Run_groups() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupIntgSuite) TestBackup_Run_incrementalGroups() {
|
func (suite *GroupsBackupIntgSuite) TestBackup_Run_incrementalGroups() {
|
||||||
runGroupsIncrementalBackupTests(suite, suite.its, control.DefaultOptions())
|
runGroupsIncrementalBackupTests(suite, suite.m365, control.DefaultOptions())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupIntgSuite) TestBackup_Run_extensionsGroups() {
|
func (suite *GroupsBackupIntgSuite) TestBackup_Run_extensionsGroups() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Group.ID
|
resourceID = suite.m365.Group.ID
|
||||||
sel = selectors.NewGroupsBackup([]string{resourceID})
|
sel = selectors.NewGroupsBackup([]string{resourceID})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,7 +85,7 @@ func (suite *GroupsBackupIntgSuite) TestBackup_Run_extensionsGroups() {
|
|||||||
|
|
||||||
type GroupsBackupTreeIntgSuite struct {
|
type GroupsBackupTreeIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupsBackupTreeIntgSuite(t *testing.T) {
|
func TestGroupsBackupTreeIntgSuite(t *testing.T) {
|
||||||
@ -96,12 +97,12 @@ func TestGroupsBackupTreeIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupTreeIntgSuite) SetupSuite() {
|
func (suite *GroupsBackupTreeIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupTreeIntgSuite) TestBackup_Run_treeGroups() {
|
func (suite *GroupsBackupTreeIntgSuite) TestBackup_Run_treeGroups() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Group.ID
|
resourceID = suite.m365.Group.ID
|
||||||
sel = selectors.NewGroupsBackup([]string{resourceID})
|
sel = selectors.NewGroupsBackup([]string{resourceID})
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
)
|
)
|
||||||
@ -117,12 +118,12 @@ func (suite *GroupsBackupTreeIntgSuite) TestBackup_Run_treeGroups() {
|
|||||||
|
|
||||||
func (suite *GroupsBackupTreeIntgSuite) TestBackup_Run_treeIncrementalGroups() {
|
func (suite *GroupsBackupTreeIntgSuite) TestBackup_Run_treeIncrementalGroups() {
|
||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
runGroupsIncrementalBackupTests(suite, suite.its, opts)
|
runGroupsIncrementalBackupTests(suite, suite.m365, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupTreeIntgSuite) TestBackup_Run_treeExtensionsGroups() {
|
func (suite *GroupsBackupTreeIntgSuite) TestBackup_Run_treeExtensionsGroups() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Group.ID
|
resourceID = suite.m365.Group.ID
|
||||||
sel = selectors.NewGroupsBackup([]string{resourceID})
|
sel = selectors.NewGroupsBackup([]string{resourceID})
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
)
|
)
|
||||||
@ -142,10 +143,10 @@ func (suite *GroupsBackupTreeIntgSuite) TestBackup_Run_treeExtensionsGroups() {
|
|||||||
|
|
||||||
func runGroupsIncrementalBackupTests(
|
func runGroupsIncrementalBackupTests(
|
||||||
suite tester.Suite,
|
suite tester.Suite,
|
||||||
its IntgTesterSetup,
|
m365 its.M365IntgTestSetup,
|
||||||
opts control.Options,
|
opts control.Options,
|
||||||
) {
|
) {
|
||||||
sel := selectors.NewGroupsRestore([]string{its.Group.ID})
|
sel := selectors.NewGroupsRestore([]string{m365.Group.ID})
|
||||||
|
|
||||||
ic := func(cs []string) selectors.Selector {
|
ic := func(cs []string) selectors.Selector {
|
||||||
sel.Include(sel.LibraryFolders(cs, selectors.PrefixMatch()))
|
sel.Include(sel.LibraryFolders(cs, selectors.PrefixMatch()))
|
||||||
@ -156,14 +157,14 @@ func runGroupsIncrementalBackupTests(
|
|||||||
t *testing.T,
|
t *testing.T,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) string {
|
) string {
|
||||||
return its.Group.RootSite.DriveID
|
return m365.Group.RootSite.DriveID
|
||||||
}
|
}
|
||||||
|
|
||||||
gtsi := func(
|
gtsi := func(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) string {
|
) string {
|
||||||
return its.Group.RootSite.ID
|
return m365.Group.RootSite.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
grh := func(ac api.Client) drive.RestoreHandler {
|
grh := func(ac api.Client) drive.RestoreHandler {
|
||||||
@ -173,8 +174,8 @@ func runGroupsIncrementalBackupTests(
|
|||||||
RunIncrementalDriveishBackupTest(
|
RunIncrementalDriveishBackupTest(
|
||||||
suite,
|
suite,
|
||||||
opts,
|
opts,
|
||||||
its.Group.ID,
|
m365.Group.ID,
|
||||||
its.User.ID,
|
m365.SecondaryGroup.ID, // more reliable than user
|
||||||
path.GroupsService,
|
path.GroupsService,
|
||||||
path.LibrariesCategory,
|
path.LibrariesCategory,
|
||||||
ic,
|
ic,
|
||||||
@ -193,15 +194,19 @@ func (suite *GroupsBackupIntgSuite) TestBackup_Run_groupsBasic() {
|
|||||||
var (
|
var (
|
||||||
mb = evmock.NewBus()
|
mb = evmock.NewBus()
|
||||||
counter = count.New()
|
counter = count.New()
|
||||||
sel = selectors.NewGroupsBackup([]string{suite.its.Group.ID})
|
sel = selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
whatSet = deeTD.CategoryFromRepoRef
|
whatSet = deeTD.CategoryFromRepoRef
|
||||||
)
|
)
|
||||||
|
|
||||||
sel.Include(
|
sel.Include(
|
||||||
selTD.GroupsBackupLibraryFolderScope(sel),
|
selTD.GroupsBackupLibraryFolderScope(sel),
|
||||||
selTD.GroupsBackupChannelScope(sel),
|
selTD.GroupsBackupChannelScope(sel))
|
||||||
selTD.GroupsBackupConversationScope(sel))
|
|
||||||
|
// TODO(pandeyabs): CorsoCITeam group mailbox backup is currently broken because of invalid
|
||||||
|
// odata.NextLink which causes an infinite loop during paging. Disabling conversations tests while
|
||||||
|
// we go fix the group mailbox.
|
||||||
|
// selTD.GroupsBackupConversationScope(sel))
|
||||||
|
|
||||||
bo, bod := PrepNewTestBackupOp(t, ctx, mb, sel.Selector, opts, version.Backup, counter)
|
bo, bod := PrepNewTestBackupOp(t, ctx, mb, sel.Selector, opts, version.Backup, counter)
|
||||||
defer bod.Close(t, ctx)
|
defer bod.Close(t, ctx)
|
||||||
@ -306,7 +311,7 @@ func (suite *GroupsBackupIntgSuite) TestBackup_Run_groupsBasic() {
|
|||||||
|
|
||||||
type GroupsBackupNightlyIntgSuite struct {
|
type GroupsBackupNightlyIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupsBackupNightlyIntgSuite(t *testing.T) {
|
func TestGroupsBackupNightlyIntgSuite(t *testing.T) {
|
||||||
@ -318,32 +323,40 @@ func TestGroupsBackupNightlyIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupNightlyIntgSuite) SetupSuite() {
|
func (suite *GroupsBackupNightlyIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupNightlyIntgSuite) TestBackup_Run_groupsVersion9MergeBase() {
|
func (suite *GroupsBackupNightlyIntgSuite) TestBackup_Run_groupsVersion9MergeBase() {
|
||||||
sel := selectors.NewGroupsBackup([]string{suite.its.Group.ID})
|
sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
|
||||||
sel.Include(
|
sel.Include(
|
||||||
selTD.GroupsBackupLibraryFolderScope(sel),
|
selTD.GroupsBackupLibraryFolderScope(sel),
|
||||||
selTD.GroupsBackupChannelScope(sel),
|
selTD.GroupsBackupChannelScope(sel))
|
||||||
selTD.GroupsBackupConversationScope(sel))
|
|
||||||
|
// TODO(pandeyabs): CorsoCITeam group mailbox backup is currently broken because of invalid
|
||||||
|
// odata.NextLink which causes an infinite loop during paging. Disabling conv backups while
|
||||||
|
// we go fix the group mailbox.
|
||||||
|
// selTD.GroupsBackupConversationScope(sel))
|
||||||
|
|
||||||
RunMergeBaseGroupsUpdate(suite, sel.Selector, false)
|
RunMergeBaseGroupsUpdate(suite, sel.Selector, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsBackupNightlyIntgSuite) TestBackup_Run_groupsVersion9AssistBases() {
|
func (suite *GroupsBackupNightlyIntgSuite) TestBackup_Run_groupsVersion9AssistBases() {
|
||||||
sel := selectors.NewGroupsBackup([]string{suite.its.Group.ID})
|
sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
|
||||||
sel.Include(
|
sel.Include(
|
||||||
selTD.GroupsBackupLibraryFolderScope(sel),
|
selTD.GroupsBackupLibraryFolderScope(sel),
|
||||||
selTD.GroupsBackupChannelScope(sel),
|
selTD.GroupsBackupChannelScope(sel))
|
||||||
selTD.GroupsBackupConversationScope(sel))
|
|
||||||
|
// TODO(pandeyabs): CorsoCITeam group mailbox backup is currently broken because of invalid
|
||||||
|
// odata.NextLink which causes an infinite loop during paging. Disabling conv backups while
|
||||||
|
// we go fix the group mailbox.
|
||||||
|
// selTD.GroupsBackupConversationScope(sel))
|
||||||
|
|
||||||
RunDriveAssistBaseGroupsUpdate(suite, sel.Selector, false)
|
RunDriveAssistBaseGroupsUpdate(suite, sel.Selector, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupsRestoreNightlyIntgSuite struct {
|
type GroupsRestoreNightlyIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGroupsRestoreIntgSuite(t *testing.T) {
|
func TestGroupsRestoreIntgSuite(t *testing.T) {
|
||||||
@ -355,20 +368,20 @@ func TestGroupsRestoreIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsRestoreNightlyIntgSuite) SetupSuite() {
|
func (suite *GroupsRestoreNightlyIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GroupsRestoreNightlyIntgSuite) TestRestore_Run_groupsWithAdvancedOptions() {
|
func (suite *GroupsRestoreNightlyIntgSuite) TestRestore_Run_groupsWithAdvancedOptions() {
|
||||||
sel := selectors.NewGroupsBackup([]string{suite.its.Group.ID})
|
sel := selectors.NewGroupsBackup([]string{suite.m365.Group.ID})
|
||||||
sel.Include(selTD.GroupsBackupLibraryFolderScope(sel))
|
sel.Include(selTD.GroupsBackupLibraryFolderScope(sel))
|
||||||
sel.Filter(sel.Library("documents"))
|
sel.Filter(sel.Library("documents"))
|
||||||
sel.DiscreteOwner = suite.its.Group.ID
|
sel.DiscreteOwner = suite.m365.Group.ID
|
||||||
|
|
||||||
RunDriveRestoreWithAdvancedOptions(
|
RunDriveRestoreWithAdvancedOptions(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
suite,
|
suite,
|
||||||
suite.its.AC,
|
suite.m365.AC,
|
||||||
sel.Selector,
|
sel.Selector,
|
||||||
suite.its.Group.RootSite.DriveID,
|
suite.m365.Group.RootSite.DriveID,
|
||||||
suite.its.Group.RootSite.DriveRootFolderID)
|
suite.m365.Group.RootSite.DriveRootFolderID)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/kopia"
|
"github.com/alcionai/corso/src/internal/kopia"
|
||||||
"github.com/alcionai/corso/src/internal/m365"
|
"github.com/alcionai/corso/src/internal/m365"
|
||||||
@ -19,8 +18,6 @@ import (
|
|||||||
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
odConsts "github.com/alcionai/corso/src/internal/m365/service/onedrive/consts"
|
||||||
"github.com/alcionai/corso/src/internal/model"
|
"github.com/alcionai/corso/src/internal/model"
|
||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
@ -357,125 +354,6 @@ func ControllerWithSelector(
|
|||||||
// Suite Setup
|
// Suite Setup
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type IDs struct {
|
|
||||||
ID string
|
|
||||||
DriveID string
|
|
||||||
DriveRootFolderID string
|
|
||||||
}
|
|
||||||
|
|
||||||
type GIDs struct {
|
|
||||||
ID string
|
|
||||||
RootSite IDs
|
|
||||||
}
|
|
||||||
|
|
||||||
type IntgTesterSetup struct {
|
|
||||||
AC api.Client
|
|
||||||
GockAC api.Client
|
|
||||||
User IDs
|
|
||||||
SecondaryUser IDs
|
|
||||||
Site IDs
|
|
||||||
SecondarySite IDs
|
|
||||||
Group GIDs
|
|
||||||
SecondaryGroup GIDs
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIntegrationTesterSetup(t *testing.T) IntgTesterSetup {
|
|
||||||
its := IntgTesterSetup{}
|
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
|
||||||
|
|
||||||
a := tconfig.NewM365Account(t)
|
|
||||||
creds, err := a.M365Config()
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
counter := count.New()
|
|
||||||
|
|
||||||
its.AC, err = api.NewClient(
|
|
||||||
creds,
|
|
||||||
control.DefaultOptions(),
|
|
||||||
counter)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
its.GockAC, err = GockClient(creds, counter)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
its.User = userIDs(t, tconfig.M365UserID(t), its.AC)
|
|
||||||
its.SecondaryUser = userIDs(t, tconfig.SecondaryM365UserID(t), its.AC)
|
|
||||||
its.Site = siteIDs(t, tconfig.M365SiteID(t), its.AC)
|
|
||||||
its.SecondarySite = siteIDs(t, tconfig.SecondaryM365SiteID(t), its.AC)
|
|
||||||
// teamID is used here intentionally. We want the group
|
|
||||||
// to have access to teams data
|
|
||||||
its.Group = groupIDs(t, tconfig.M365TeamID(t), its.AC)
|
|
||||||
its.SecondaryGroup = groupIDs(t, tconfig.SecondaryM365TeamID(t), its.AC)
|
|
||||||
|
|
||||||
return its
|
|
||||||
}
|
|
||||||
|
|
||||||
func userIDs(t *testing.T, id string, ac api.Client) IDs {
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
r := IDs{ID: id}
|
|
||||||
|
|
||||||
drive, err := ac.Users().GetDefaultDrive(ctx, id)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
r.DriveID = ptr.Val(drive.GetId())
|
|
||||||
|
|
||||||
driveRootFolder, err := ac.Drives().GetRootFolder(ctx, r.DriveID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
r.DriveRootFolderID = ptr.Val(driveRootFolder.GetId())
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func siteIDs(t *testing.T, id string, ac api.Client) IDs {
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
r := IDs{ID: id}
|
|
||||||
|
|
||||||
drive, err := ac.Sites().GetDefaultDrive(ctx, id)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
r.DriveID = ptr.Val(drive.GetId())
|
|
||||||
|
|
||||||
driveRootFolder, err := ac.Drives().GetRootFolder(ctx, r.DriveID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
r.DriveRootFolderID = ptr.Val(driveRootFolder.GetId())
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupIDs(t *testing.T, id string, ac api.Client) GIDs {
|
|
||||||
ctx, flush := tester.NewContext(t)
|
|
||||||
defer flush()
|
|
||||||
|
|
||||||
r := GIDs{ID: id}
|
|
||||||
|
|
||||||
site, err := ac.Groups().GetRootSite(ctx, id)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
r.RootSite.ID = ptr.Val(site.GetId())
|
|
||||||
|
|
||||||
drive, err := ac.Sites().GetDefaultDrive(ctx, r.RootSite.ID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
r.RootSite.DriveID = ptr.Val(drive.GetId())
|
|
||||||
|
|
||||||
driveRootFolder, err := ac.Drives().GetRootFolder(ctx, r.RootSite.DriveID)
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
|
||||||
|
|
||||||
r.RootSite.DriveRootFolderID = ptr.Val(driveRootFolder.GetId())
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTestExtensionFactories() []extensions.CreateItemExtensioner {
|
func GetTestExtensionFactories() []extensions.CreateItemExtensioner {
|
||||||
return []extensions.CreateItemExtensioner{
|
return []extensions.CreateItemExtensioner{
|
||||||
&extensions.MockItemExtensionFactory{},
|
&extensions.MockItemExtensionFactory{},
|
||||||
|
|||||||
@ -13,12 +13,13 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/events"
|
"github.com/alcionai/corso/src/internal/events"
|
||||||
evmock "github.com/alcionai/corso/src/internal/events/mock"
|
evmock "github.com/alcionai/corso/src/internal/events/mock"
|
||||||
"github.com/alcionai/corso/src/internal/m365"
|
m365Ctrl "github.com/alcionai/corso/src/internal/m365"
|
||||||
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
||||||
"github.com/alcionai/corso/src/internal/model"
|
"github.com/alcionai/corso/src/internal/model"
|
||||||
. "github.com/alcionai/corso/src/internal/operations/test/m365"
|
. "github.com/alcionai/corso/src/internal/operations/test/m365"
|
||||||
"github.com/alcionai/corso/src/internal/streamstore"
|
"github.com/alcionai/corso/src/internal/streamstore"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
@ -36,7 +37,7 @@ import (
|
|||||||
|
|
||||||
type OneDriveBackupIntgSuite struct {
|
type OneDriveBackupIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOneDriveBackupIntgSuite(t *testing.T) {
|
func TestOneDriveBackupIntgSuite(t *testing.T) {
|
||||||
@ -48,12 +49,12 @@ func TestOneDriveBackupIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupIntgSuite) SetupSuite() {
|
func (suite *OneDriveBackupIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupIntgSuite) TestBackup_Run_oneDrive() {
|
func (suite *OneDriveBackupIntgSuite) TestBackup_Run_oneDrive() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.SecondaryUser.ID
|
resourceID = suite.m365.SecondaryUser.ID
|
||||||
sel = selectors.NewOneDriveBackup([]string{resourceID})
|
sel = selectors.NewOneDriveBackup([]string{resourceID})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,12 +68,12 @@ func (suite *OneDriveBackupIntgSuite) TestBackup_Run_oneDrive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupIntgSuite) TestBackup_Run_incrementalOneDrive() {
|
func (suite *OneDriveBackupIntgSuite) TestBackup_Run_incrementalOneDrive() {
|
||||||
runOneDriveIncrementalBackupTests(suite, suite.its, control.DefaultOptions())
|
runOneDriveIncrementalBackupTests(suite, suite.m365, control.DefaultOptions())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupIntgSuite) TestBackup_Run_extensionsOneDrive() {
|
func (suite *OneDriveBackupIntgSuite) TestBackup_Run_extensionsOneDrive() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.SecondaryUser.ID
|
resourceID = suite.m365.SecondaryUser.ID
|
||||||
sel = selectors.NewOneDriveBackup([]string{resourceID})
|
sel = selectors.NewOneDriveBackup([]string{resourceID})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ func (suite *OneDriveBackupIntgSuite) TestBackup_Run_extensionsOneDrive() {
|
|||||||
|
|
||||||
type OneDriveBackupTreeIntgSuite struct {
|
type OneDriveBackupTreeIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOneDriveBackupTreeIntgSuite(t *testing.T) {
|
func TestOneDriveBackupTreeIntgSuite(t *testing.T) {
|
||||||
@ -103,12 +104,12 @@ func TestOneDriveBackupTreeIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupTreeIntgSuite) SetupSuite() {
|
func (suite *OneDriveBackupTreeIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupTreeIntgSuite) TestBackup_Run_treeOneDrive() {
|
func (suite *OneDriveBackupTreeIntgSuite) TestBackup_Run_treeOneDrive() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.SecondaryUser.ID
|
resourceID = suite.m365.SecondaryUser.ID
|
||||||
sel = selectors.NewOneDriveBackup([]string{resourceID})
|
sel = selectors.NewOneDriveBackup([]string{resourceID})
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
)
|
)
|
||||||
@ -124,12 +125,12 @@ func (suite *OneDriveBackupTreeIntgSuite) TestBackup_Run_treeOneDrive() {
|
|||||||
|
|
||||||
func (suite *OneDriveBackupTreeIntgSuite) TestBackup_Run_treeIncrementalOneDrive() {
|
func (suite *OneDriveBackupTreeIntgSuite) TestBackup_Run_treeIncrementalOneDrive() {
|
||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
runOneDriveIncrementalBackupTests(suite, suite.its, opts)
|
runOneDriveIncrementalBackupTests(suite, suite.m365, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupTreeIntgSuite) TestBackup_Run_treeExtensionsOneDrive() {
|
func (suite *OneDriveBackupTreeIntgSuite) TestBackup_Run_treeExtensionsOneDrive() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.SecondaryUser.ID
|
resourceID = suite.m365.SecondaryUser.ID
|
||||||
sel = selectors.NewOneDriveBackup([]string{resourceID})
|
sel = selectors.NewOneDriveBackup([]string{resourceID})
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
)
|
)
|
||||||
@ -149,10 +150,10 @@ func (suite *OneDriveBackupTreeIntgSuite) TestBackup_Run_treeExtensionsOneDrive(
|
|||||||
|
|
||||||
func runOneDriveIncrementalBackupTests(
|
func runOneDriveIncrementalBackupTests(
|
||||||
suite tester.Suite,
|
suite tester.Suite,
|
||||||
its IntgTesterSetup,
|
m365 its.M365IntgTestSetup,
|
||||||
opts control.Options,
|
opts control.Options,
|
||||||
) {
|
) {
|
||||||
sel := selectors.NewOneDriveRestore([]string{its.User.ID})
|
sel := selectors.NewOneDriveRestore([]string{m365.User.ID})
|
||||||
|
|
||||||
ic := func(cs []string) selectors.Selector {
|
ic := func(cs []string) selectors.Selector {
|
||||||
sel.Include(sel.Folders(cs, selectors.PrefixMatch()))
|
sel.Include(sel.Folders(cs, selectors.PrefixMatch()))
|
||||||
@ -163,10 +164,10 @@ func runOneDriveIncrementalBackupTests(
|
|||||||
t *testing.T,
|
t *testing.T,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) string {
|
) string {
|
||||||
d, err := its.AC.Users().GetDefaultDrive(ctx, its.User.ID)
|
d, err := m365.AC.Users().GetDefaultDrive(ctx, m365.User.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = clues.Wrap(err, "retrieving default user drive").
|
err = clues.Wrap(err, "retrieving default user drive").
|
||||||
With("user", its.User.ID)
|
With("user", m365.User.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
@ -184,8 +185,8 @@ func runOneDriveIncrementalBackupTests(
|
|||||||
RunIncrementalDriveishBackupTest(
|
RunIncrementalDriveishBackupTest(
|
||||||
suite,
|
suite,
|
||||||
opts,
|
opts,
|
||||||
its.User.ID,
|
m365.User.ID,
|
||||||
its.User.ID,
|
m365.User.ID,
|
||||||
path.OneDriveService,
|
path.OneDriveService,
|
||||||
path.FilesCategory,
|
path.FilesCategory,
|
||||||
ic,
|
ic,
|
||||||
@ -219,7 +220,7 @@ func (suite *OneDriveBackupIntgSuite) TestBackup_Run_oneDriveOwnerMigration() {
|
|||||||
creds, err := acct.M365Config()
|
creds, err := acct.M365Config()
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
ctrl, err := m365.NewController(
|
ctrl, err := m365Ctrl.NewController(
|
||||||
ctx,
|
ctx,
|
||||||
acct,
|
acct,
|
||||||
path.OneDriveService,
|
path.OneDriveService,
|
||||||
@ -229,7 +230,7 @@ func (suite *OneDriveBackupIntgSuite) TestBackup_Run_oneDriveOwnerMigration() {
|
|||||||
|
|
||||||
userable, err := ctrl.AC.Users().GetByID(
|
userable, err := ctrl.AC.Users().GetByID(
|
||||||
ctx,
|
ctx,
|
||||||
suite.its.User.ID,
|
suite.m365.User.ID,
|
||||||
api.CallConfig{})
|
api.CallConfig{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
@ -333,7 +334,7 @@ func (suite *OneDriveBackupIntgSuite) TestBackup_Run_oneDriveOwnerMigration() {
|
|||||||
|
|
||||||
type OneDriveBackupNightlyIntgSuite struct {
|
type OneDriveBackupNightlyIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOneDriveBackupNightlyIntgSuite(t *testing.T) {
|
func TestOneDriveBackupNightlyIntgSuite(t *testing.T) {
|
||||||
@ -345,11 +346,11 @@ func TestOneDriveBackupNightlyIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupNightlyIntgSuite) SetupSuite() {
|
func (suite *OneDriveBackupNightlyIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveBackupNightlyIntgSuite) TestBackup_Run_oneDriveVersion9MergeBase() {
|
func (suite *OneDriveBackupNightlyIntgSuite) TestBackup_Run_oneDriveVersion9MergeBase() {
|
||||||
sel := selectors.NewOneDriveBackup([]string{suite.its.User.ID})
|
sel := selectors.NewOneDriveBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(selTD.OneDriveBackupFolderScope(sel))
|
sel.Include(selTD.OneDriveBackupFolderScope(sel))
|
||||||
|
|
||||||
RunMergeBaseGroupsUpdate(suite, sel.Selector, true)
|
RunMergeBaseGroupsUpdate(suite, sel.Selector, true)
|
||||||
@ -364,7 +365,7 @@ func (suite *OneDriveBackupNightlyIntgSuite) TestBackup_Run_oneDriveVersion9Merg
|
|||||||
|
|
||||||
type OneDriveRestoreNightlyIntgSuite struct {
|
type OneDriveRestoreNightlyIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOneDriveRestoreIntgSuite(t *testing.T) {
|
func TestOneDriveRestoreIntgSuite(t *testing.T) {
|
||||||
@ -376,34 +377,34 @@ func TestOneDriveRestoreIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveRestoreNightlyIntgSuite) SetupSuite() {
|
func (suite *OneDriveRestoreNightlyIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveRestoreNightlyIntgSuite) TestRestore_Run_onedriveWithAdvancedOptions() {
|
func (suite *OneDriveRestoreNightlyIntgSuite) TestRestore_Run_onedriveWithAdvancedOptions() {
|
||||||
sel := selectors.NewOneDriveBackup([]string{suite.its.User.ID})
|
sel := selectors.NewOneDriveBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(selTD.OneDriveBackupFolderScope(sel))
|
sel.Include(selTD.OneDriveBackupFolderScope(sel))
|
||||||
sel.DiscreteOwner = suite.its.User.ID
|
sel.DiscreteOwner = suite.m365.User.ID
|
||||||
|
|
||||||
RunDriveRestoreWithAdvancedOptions(
|
RunDriveRestoreWithAdvancedOptions(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
suite,
|
suite,
|
||||||
suite.its.AC,
|
suite.m365.AC,
|
||||||
sel.Selector,
|
sel.Selector,
|
||||||
suite.its.User.DriveID,
|
suite.m365.User.DriveID,
|
||||||
suite.its.User.DriveRootFolderID)
|
suite.m365.User.DriveRootFolderID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *OneDriveRestoreNightlyIntgSuite) TestRestore_Run_onedriveAlternateProtectedResource() {
|
func (suite *OneDriveRestoreNightlyIntgSuite) TestRestore_Run_onedriveAlternateProtectedResource() {
|
||||||
sel := selectors.NewOneDriveBackup([]string{suite.its.User.ID})
|
sel := selectors.NewOneDriveBackup([]string{suite.m365.User.ID})
|
||||||
sel.Include(selTD.OneDriveBackupFolderScope(sel))
|
sel.Include(selTD.OneDriveBackupFolderScope(sel))
|
||||||
sel.DiscreteOwner = suite.its.User.ID
|
sel.DiscreteOwner = suite.m365.User.ID
|
||||||
|
|
||||||
RunDriveRestoreToAlternateProtectedResource(
|
RunDriveRestoreToAlternateProtectedResource(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
suite,
|
suite,
|
||||||
suite.its.AC,
|
suite.m365.AC,
|
||||||
sel.Selector,
|
sel.Selector,
|
||||||
suite.its.User,
|
suite.m365.User,
|
||||||
suite.its.SecondaryUser,
|
suite.m365.SecondaryUser,
|
||||||
suite.its.SecondaryUser.ID)
|
suite.m365.SecondaryUser.ID)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
"github.com/alcionai/corso/src/internal/m365/collection/drive"
|
||||||
. "github.com/alcionai/corso/src/internal/operations/test/m365"
|
. "github.com/alcionai/corso/src/internal/operations/test/m365"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
deeTD "github.com/alcionai/corso/src/pkg/backup/details/testdata"
|
deeTD "github.com/alcionai/corso/src/pkg/backup/details/testdata"
|
||||||
@ -31,7 +32,7 @@ import (
|
|||||||
|
|
||||||
type SharePointBackupIntgSuite struct {
|
type SharePointBackupIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSharePointBackupIntgSuite(t *testing.T) {
|
func TestSharePointBackupIntgSuite(t *testing.T) {
|
||||||
@ -43,12 +44,12 @@ func TestSharePointBackupIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupIntgSuite) SetupSuite() {
|
func (suite *SharePointBackupIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupIntgSuite) TestBackup_Run_sharePoint() {
|
func (suite *SharePointBackupIntgSuite) TestBackup_Run_sharePoint() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Site.ID
|
resourceID = suite.m365.Site.ID
|
||||||
sel = selectors.NewSharePointBackup([]string{resourceID})
|
sel = selectors.NewSharePointBackup([]string{resourceID})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ func (suite *SharePointBackupIntgSuite) TestBackup_Run_sharePointList() {
|
|||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Site.ID
|
resourceID = suite.m365.Site.ID
|
||||||
sel = selectors.NewSharePointBackup([]string{resourceID})
|
sel = selectors.NewSharePointBackup([]string{resourceID})
|
||||||
tenID = tconfig.M365TenantID(t)
|
tenID = tconfig.M365TenantID(t)
|
||||||
mb = evmock.NewBus()
|
mb = evmock.NewBus()
|
||||||
@ -123,12 +124,12 @@ func (suite *SharePointBackupIntgSuite) TestBackup_Run_sharePointList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupIntgSuite) TestBackup_Run_incrementalSharePoint() {
|
func (suite *SharePointBackupIntgSuite) TestBackup_Run_incrementalSharePoint() {
|
||||||
runSharePointIncrementalBackupTests(suite, suite.its, control.DefaultOptions())
|
runSharePointIncrementalBackupTests(suite, suite.m365, control.DefaultOptions())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupIntgSuite) TestBackup_Run_extensionsSharePoint() {
|
func (suite *SharePointBackupIntgSuite) TestBackup_Run_extensionsSharePoint() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Site.ID
|
resourceID = suite.m365.Site.ID
|
||||||
sel = selectors.NewSharePointBackup([]string{resourceID})
|
sel = selectors.NewSharePointBackup([]string{resourceID})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -147,7 +148,7 @@ func (suite *SharePointBackupIntgSuite) TestBackup_Run_extensionsSharePoint() {
|
|||||||
|
|
||||||
type SharePointBackupTreeIntgSuite struct {
|
type SharePointBackupTreeIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSharePointBackupTreeIntgSuite(t *testing.T) {
|
func TestSharePointBackupTreeIntgSuite(t *testing.T) {
|
||||||
@ -159,12 +160,12 @@ func TestSharePointBackupTreeIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupTreeIntgSuite) SetupSuite() {
|
func (suite *SharePointBackupTreeIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupTreeIntgSuite) TestBackup_Run_treeSharePoint() {
|
func (suite *SharePointBackupTreeIntgSuite) TestBackup_Run_treeSharePoint() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Site.ID
|
resourceID = suite.m365.Site.ID
|
||||||
sel = selectors.NewSharePointBackup([]string{resourceID})
|
sel = selectors.NewSharePointBackup([]string{resourceID})
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
)
|
)
|
||||||
@ -180,12 +181,12 @@ func (suite *SharePointBackupTreeIntgSuite) TestBackup_Run_treeSharePoint() {
|
|||||||
|
|
||||||
func (suite *SharePointBackupTreeIntgSuite) TestBackup_Run_treeIncrementalSharePoint() {
|
func (suite *SharePointBackupTreeIntgSuite) TestBackup_Run_treeIncrementalSharePoint() {
|
||||||
opts := control.DefaultOptions()
|
opts := control.DefaultOptions()
|
||||||
runSharePointIncrementalBackupTests(suite, suite.its, opts)
|
runSharePointIncrementalBackupTests(suite, suite.m365, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupTreeIntgSuite) TestBackup_Run_treeExtensionsSharePoint() {
|
func (suite *SharePointBackupTreeIntgSuite) TestBackup_Run_treeExtensionsSharePoint() {
|
||||||
var (
|
var (
|
||||||
resourceID = suite.its.Site.ID
|
resourceID = suite.m365.Site.ID
|
||||||
sel = selectors.NewSharePointBackup([]string{resourceID})
|
sel = selectors.NewSharePointBackup([]string{resourceID})
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
)
|
)
|
||||||
@ -205,10 +206,10 @@ func (suite *SharePointBackupTreeIntgSuite) TestBackup_Run_treeExtensionsSharePo
|
|||||||
|
|
||||||
func runSharePointIncrementalBackupTests(
|
func runSharePointIncrementalBackupTests(
|
||||||
suite tester.Suite,
|
suite tester.Suite,
|
||||||
its IntgTesterSetup,
|
m365 its.M365IntgTestSetup,
|
||||||
opts control.Options,
|
opts control.Options,
|
||||||
) {
|
) {
|
||||||
sel := selectors.NewSharePointRestore([]string{its.Site.ID})
|
sel := selectors.NewSharePointRestore([]string{m365.Site.ID})
|
||||||
|
|
||||||
ic := func(cs []string) selectors.Selector {
|
ic := func(cs []string) selectors.Selector {
|
||||||
sel.Include(sel.LibraryFolders(cs, selectors.PrefixMatch()))
|
sel.Include(sel.LibraryFolders(cs, selectors.PrefixMatch()))
|
||||||
@ -219,10 +220,10 @@ func runSharePointIncrementalBackupTests(
|
|||||||
t *testing.T,
|
t *testing.T,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) string {
|
) string {
|
||||||
d, err := its.AC.Sites().GetDefaultDrive(ctx, its.Site.ID)
|
d, err := m365.AC.Sites().GetDefaultDrive(ctx, m365.Site.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = clues.Wrap(err, "retrieving default site drive").
|
err = clues.Wrap(err, "retrieving default site drive").
|
||||||
With("site", its.Site.ID)
|
With("site", m365.Site.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
@ -240,8 +241,8 @@ func runSharePointIncrementalBackupTests(
|
|||||||
RunIncrementalDriveishBackupTest(
|
RunIncrementalDriveishBackupTest(
|
||||||
suite,
|
suite,
|
||||||
opts,
|
opts,
|
||||||
its.Site.ID,
|
m365.Site.ID,
|
||||||
its.User.ID,
|
m365.User.ID,
|
||||||
path.SharePointService,
|
path.SharePointService,
|
||||||
path.LibrariesCategory,
|
path.LibrariesCategory,
|
||||||
ic,
|
ic,
|
||||||
@ -253,7 +254,7 @@ func runSharePointIncrementalBackupTests(
|
|||||||
|
|
||||||
type SharePointBackupNightlyIntgSuite struct {
|
type SharePointBackupNightlyIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSharePointBackupNightlyIntgSuite(t *testing.T) {
|
func TestSharePointBackupNightlyIntgSuite(t *testing.T) {
|
||||||
@ -265,18 +266,18 @@ func TestSharePointBackupNightlyIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupNightlyIntgSuite) SetupSuite() {
|
func (suite *SharePointBackupNightlyIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupNightlyIntgSuite) TestBackup_Run_sharePointVersion9MergeBase() {
|
func (suite *SharePointBackupNightlyIntgSuite) TestBackup_Run_sharePointVersion9MergeBase() {
|
||||||
sel := selectors.NewSharePointBackup([]string{suite.its.Site.ID})
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
sel.Include(selTD.SharePointBackupFolderScope(sel))
|
sel.Include(selTD.SharePointBackupFolderScope(sel))
|
||||||
|
|
||||||
RunMergeBaseGroupsUpdate(suite, sel.Selector, true)
|
RunMergeBaseGroupsUpdate(suite, sel.Selector, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointBackupNightlyIntgSuite) TestBackup_Run_sharePointVersion9AssistBases() {
|
func (suite *SharePointBackupNightlyIntgSuite) TestBackup_Run_sharePointVersion9AssistBases() {
|
||||||
sel := selectors.NewSharePointBackup([]string{suite.its.Site.ID})
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
sel.Include(selTD.SharePointBackupFolderScope(sel))
|
sel.Include(selTD.SharePointBackupFolderScope(sel))
|
||||||
|
|
||||||
RunDriveAssistBaseGroupsUpdate(suite, sel.Selector, true)
|
RunDriveAssistBaseGroupsUpdate(suite, sel.Selector, true)
|
||||||
@ -284,7 +285,7 @@ func (suite *SharePointBackupNightlyIntgSuite) TestBackup_Run_sharePointVersion9
|
|||||||
|
|
||||||
type SharePointRestoreNightlyIntgSuite struct {
|
type SharePointRestoreNightlyIntgSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
its IntgTesterSetup
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSharePointRestoreIntgSuite(t *testing.T) {
|
func TestSharePointRestoreIntgSuite(t *testing.T) {
|
||||||
@ -296,38 +297,38 @@ func TestSharePointRestoreIntgSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointRestoreNightlyIntgSuite) SetupSuite() {
|
func (suite *SharePointRestoreNightlyIntgSuite) SetupSuite() {
|
||||||
suite.its = NewIntegrationTesterSetup(suite.T())
|
suite.m365 = its.GetM365(suite.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointWithAdvancedOptions() {
|
func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointWithAdvancedOptions() {
|
||||||
sel := selectors.NewSharePointBackup([]string{suite.its.Site.ID})
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
sel.Include(selTD.SharePointBackupFolderScope(sel))
|
sel.Include(selTD.SharePointBackupFolderScope(sel))
|
||||||
sel.Filter(sel.Library("documents"))
|
sel.Filter(sel.Library("documents"))
|
||||||
sel.DiscreteOwner = suite.its.Site.ID
|
sel.DiscreteOwner = suite.m365.Site.ID
|
||||||
|
|
||||||
RunDriveRestoreWithAdvancedOptions(
|
RunDriveRestoreWithAdvancedOptions(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
suite,
|
suite,
|
||||||
suite.its.AC,
|
suite.m365.AC,
|
||||||
sel.Selector,
|
sel.Selector,
|
||||||
suite.its.Site.DriveID,
|
suite.m365.Site.DriveID,
|
||||||
suite.its.Site.DriveRootFolderID)
|
suite.m365.Site.DriveRootFolderID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointAlternateProtectedResource() {
|
func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointAlternateProtectedResource() {
|
||||||
sel := selectors.NewSharePointBackup([]string{suite.its.Site.ID})
|
sel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
sel.Include(selTD.SharePointBackupFolderScope(sel))
|
sel.Include(selTD.SharePointBackupFolderScope(sel))
|
||||||
sel.Filter(sel.Library("documents"))
|
sel.Filter(sel.Library("documents"))
|
||||||
sel.DiscreteOwner = suite.its.Site.ID
|
sel.DiscreteOwner = suite.m365.Site.ID
|
||||||
|
|
||||||
RunDriveRestoreToAlternateProtectedResource(
|
RunDriveRestoreToAlternateProtectedResource(
|
||||||
suite.T(),
|
suite.T(),
|
||||||
suite,
|
suite,
|
||||||
suite.its.AC,
|
suite.m365.AC,
|
||||||
sel.Selector,
|
sel.Selector,
|
||||||
suite.its.Site,
|
suite.m365.Site,
|
||||||
suite.its.SecondarySite,
|
suite.m365.SecondarySite,
|
||||||
suite.its.SecondarySite.ID)
|
suite.m365.SecondarySite.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointDeletedDrives() {
|
func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointDeletedDrives() {
|
||||||
@ -344,13 +345,13 @@ func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointDelete
|
|||||||
rc.OnCollision = control.Copy
|
rc.OnCollision = control.Copy
|
||||||
|
|
||||||
// create a new drive
|
// create a new drive
|
||||||
md, err := suite.its.AC.Lists().PostDrive(ctx, suite.its.Site.ID, rc.Location)
|
md, err := suite.m365.AC.Lists().PostDrive(ctx, suite.m365.Site.ID, rc.Location)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
driveID := ptr.Val(md.GetId())
|
driveID := ptr.Val(md.GetId())
|
||||||
|
|
||||||
// get the root folder
|
// get the root folder
|
||||||
mdi, err := suite.its.AC.Drives().GetRootFolder(ctx, driveID)
|
mdi, err := suite.m365.AC.Drives().GetRootFolder(ctx, driveID)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
rootFolderID := ptr.Val(mdi.GetId())
|
rootFolderID := ptr.Val(mdi.GetId())
|
||||||
@ -364,7 +365,7 @@ func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointDelete
|
|||||||
file := models.NewFile()
|
file := models.NewFile()
|
||||||
item.SetFile(file)
|
item.SetFile(file)
|
||||||
|
|
||||||
_, err = suite.its.AC.Drives().PostItemInContainer(
|
_, err = suite.m365.AC.Drives().PostItemInContainer(
|
||||||
ctx,
|
ctx,
|
||||||
driveID,
|
driveID,
|
||||||
rootFolderID,
|
rootFolderID,
|
||||||
@ -377,13 +378,13 @@ func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointDelete
|
|||||||
mb = evmock.NewBus()
|
mb = evmock.NewBus()
|
||||||
counter = count.New()
|
counter = count.New()
|
||||||
opts = control.DefaultOptions()
|
opts = control.DefaultOptions()
|
||||||
graphClient = suite.its.AC.Stable.Client()
|
graphClient = suite.m365.AC.Stable.Client()
|
||||||
)
|
)
|
||||||
|
|
||||||
bsel := selectors.NewSharePointBackup([]string{suite.its.Site.ID})
|
bsel := selectors.NewSharePointBackup([]string{suite.m365.Site.ID})
|
||||||
bsel.Include(selTD.SharePointBackupFolderScope(bsel))
|
bsel.Include(selTD.SharePointBackupFolderScope(bsel))
|
||||||
bsel.Filter(bsel.Library(rc.Location))
|
bsel.Filter(bsel.Library(rc.Location))
|
||||||
bsel.DiscreteOwner = suite.its.Site.ID
|
bsel.DiscreteOwner = suite.m365.Site.ID
|
||||||
|
|
||||||
bo, bod := PrepNewTestBackupOp(t, ctx, mb, bsel.Selector, opts, version.Backup, counter)
|
bo, bod := PrepNewTestBackupOp(t, ctx, mb, bsel.Selector, opts, version.Backup, counter)
|
||||||
defer bod.Close(t, ctx)
|
defer bod.Close(t, ctx)
|
||||||
@ -481,9 +482,9 @@ func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointDelete
|
|||||||
RunAndCheckRestore(t, ctx, &ro, mb, false)
|
RunAndCheckRestore(t, ctx, &ro, mb, false)
|
||||||
assert.Equal(t, 1, ctr.Get(count.NewItemCreated), "restored an item")
|
assert.Equal(t, 1, ctr.Get(count.NewItemCreated), "restored an item")
|
||||||
|
|
||||||
pgr := suite.its.AC.
|
pgr := suite.m365.AC.
|
||||||
Drives().
|
Drives().
|
||||||
NewSiteDrivePager(suite.its.Site.ID, []string{"id", "name"})
|
NewSiteDrivePager(suite.m365.Site.ID, []string{"id", "name"})
|
||||||
|
|
||||||
drives, err := api.GetAllDrives(ctx, pgr)
|
drives, err := api.GetAllDrives(ctx, pgr)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
@ -502,7 +503,7 @@ func (suite *SharePointRestoreNightlyIntgSuite) TestRestore_Run_sharepointDelete
|
|||||||
md = created
|
md = created
|
||||||
driveID = ptr.Val(md.GetId())
|
driveID = ptr.Val(md.GetId())
|
||||||
|
|
||||||
mdi, err := suite.its.AC.Drives().GetRootFolder(ctx, driveID)
|
mdi, err := suite.m365.AC.Drives().GetRootFolder(ctx, driveID)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
rootFolderID = ptr.Val(mdi.GetId())
|
rootFolderID = ptr.Val(mdi.GetId())
|
||||||
|
|||||||
258
src/internal/tester/its/m365.go
Normal file
258
src/internal/tester/its/m365.go
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
package its
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/h2non/gock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
|
"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Gockable client
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GockClient produces a new exchange api client that can be
|
||||||
|
// mocked using gock.
|
||||||
|
func gockClient(creds account.M365Config, counter *count.Bus) (api.Client, error) {
|
||||||
|
s, err := graph.NewGockService(creds, counter)
|
||||||
|
if err != nil {
|
||||||
|
return api.Client{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
li, err := graph.NewGockService(creds, counter, graph.NoTimeout())
|
||||||
|
if err != nil {
|
||||||
|
return api.Client{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.Client{
|
||||||
|
Credentials: creds,
|
||||||
|
Stable: s,
|
||||||
|
LargeItem: li,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Intercepting calls with Gock
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const graphAPIHostURL = "https://graph.microsoft.com"
|
||||||
|
|
||||||
|
func V1APIURLPath(parts ...string) string {
|
||||||
|
return strings.Join(append([]string{"/v1.0"}, parts...), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func InterceptV1Path(pathParts ...string) *gock.Request {
|
||||||
|
return gock.New(graphAPIHostURL).Get(V1APIURLPath(pathParts...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Suite Setup
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type IDs struct {
|
||||||
|
Provider idname.Provider
|
||||||
|
ID string
|
||||||
|
Email string
|
||||||
|
DisplayName string
|
||||||
|
DriveID string
|
||||||
|
DriveRootFolderID string
|
||||||
|
WebURL string
|
||||||
|
// TestContainerID is used to store the ID of the primary container under
|
||||||
|
// test. Normally this will be empty, but in certain services or data
|
||||||
|
// categories, especially those where we don't have canonical containers
|
||||||
|
// to isolate for testing, we may specify a TestContainer here instead.
|
||||||
|
TestContainerID string
|
||||||
|
// a "RootSite" is used by resources that own one or more sites.
|
||||||
|
// ex: groups and teams. Each of those resources should designate
|
||||||
|
// a "root" site (aka: the "default" site). That site gets embedded
|
||||||
|
// here because we probably interface with it as its own resource
|
||||||
|
// within the drive processors.
|
||||||
|
RootSite struct {
|
||||||
|
Provider idname.Provider
|
||||||
|
ID string
|
||||||
|
DisplayName string
|
||||||
|
DriveID string
|
||||||
|
DriveRootFolderID string
|
||||||
|
WebURL string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// M365IntgTestSetup provides all the common references used in an m365 integration
|
||||||
|
// test suite. Call `its.GetM365()` to get the singleton for your test suite.
|
||||||
|
// If you're looking for unit testing setup, use `uts.GetM365()` instead.
|
||||||
|
type M365IntgTestSetup struct {
|
||||||
|
Acct account.Account
|
||||||
|
Creds account.M365Config
|
||||||
|
TenantID string
|
||||||
|
|
||||||
|
AC api.Client
|
||||||
|
GockAC api.Client
|
||||||
|
|
||||||
|
Site IDs
|
||||||
|
SecondarySite IDs
|
||||||
|
|
||||||
|
Group IDs
|
||||||
|
SecondaryGroup IDs
|
||||||
|
|
||||||
|
User IDs
|
||||||
|
SecondaryUser IDs
|
||||||
|
TertiaryUser IDs
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetM365 returns the populated its.m365 singleton.
|
||||||
|
func GetM365(t *testing.T) M365IntgTestSetup {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
setup := M365IntgTestSetup{}
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
graph.InitializeConcurrencyLimiter(ctx, true, 4)
|
||||||
|
|
||||||
|
setup.Acct = tconfig.NewM365Account(t)
|
||||||
|
setup.Creds, err = setup.Acct.M365Config()
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
setup.TenantID = setup.Creds.AzureTenantID
|
||||||
|
|
||||||
|
setup.AC, err = api.NewClient(setup.Creds, control.DefaultOptions(), count.New())
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
setup.GockAC, err = gockClient(setup.Creds, count.New())
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
// users
|
||||||
|
|
||||||
|
fillUser(t, setup.AC, tconfig.M365UserID(t), &setup.User)
|
||||||
|
fillUser(t, setup.AC, tconfig.SecondaryM365UserID(t), &setup.SecondaryUser)
|
||||||
|
fillUser(t, setup.AC, tconfig.TertiaryM365UserID(t), &setup.TertiaryUser)
|
||||||
|
|
||||||
|
// site
|
||||||
|
|
||||||
|
fillSite(t, setup.AC, tconfig.M365SiteID(t), &setup.Site)
|
||||||
|
fillSite(t, setup.AC, tconfig.SecondaryM365SiteID(t), &setup.SecondarySite)
|
||||||
|
|
||||||
|
// team
|
||||||
|
|
||||||
|
fillTeam(t, setup.AC, tconfig.M365TeamID(t), &setup.Group)
|
||||||
|
fillTeam(t, setup.AC, tconfig.SecondaryM365TeamID(t), &setup.SecondaryGroup)
|
||||||
|
|
||||||
|
return setup
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillUser(
|
||||||
|
t *testing.T,
|
||||||
|
ac api.Client,
|
||||||
|
uid string,
|
||||||
|
ids *IDs,
|
||||||
|
) {
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
ids.ID = tconfig.M365UserID(t)
|
||||||
|
|
||||||
|
user, err := ac.Users().GetByID(ctx, uid, api.CallConfig{})
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.ID = ptr.Val(user.GetId())
|
||||||
|
ids.Email = ptr.Val(user.GetUserPrincipalName())
|
||||||
|
ids.Provider = idname.NewProvider(ids.ID, ids.Email)
|
||||||
|
ids.DisplayName = ptr.Val(user.GetDisplayName())
|
||||||
|
|
||||||
|
drive, err := ac.Users().GetDefaultDrive(ctx, ids.ID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.DriveID = ptr.Val(drive.GetId())
|
||||||
|
|
||||||
|
rootFolder, err := ac.Drives().GetRootFolder(ctx, ids.DriveID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.DriveRootFolderID = ptr.Val(rootFolder.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillSite(
|
||||||
|
t *testing.T,
|
||||||
|
ac api.Client,
|
||||||
|
sid string,
|
||||||
|
ids *IDs,
|
||||||
|
) {
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
site, err := ac.Sites().GetByID(ctx, sid, api.CallConfig{})
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.ID = ptr.Val(site.GetId())
|
||||||
|
ids.WebURL = ptr.Val(site.GetWebUrl())
|
||||||
|
ids.Provider = idname.NewProvider(ids.ID, ids.WebURL)
|
||||||
|
ids.DisplayName = ptr.Val(site.GetDisplayName())
|
||||||
|
|
||||||
|
drive, err := ac.Sites().GetDefaultDrive(ctx, ids.ID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.DriveID = ptr.Val(drive.GetId())
|
||||||
|
|
||||||
|
rootFolder, err := ac.Drives().GetRootFolder(ctx, ids.DriveID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.DriveRootFolderID = ptr.Val(rootFolder.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillTeam(
|
||||||
|
t *testing.T,
|
||||||
|
ac api.Client,
|
||||||
|
gid string,
|
||||||
|
ids *IDs,
|
||||||
|
) {
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
team, err := ac.Groups().GetByID(ctx, gid, api.CallConfig{})
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.ID = ptr.Val(team.GetId())
|
||||||
|
ids.Email = ptr.Val(team.GetMail())
|
||||||
|
ids.Provider = idname.NewProvider(ids.ID, ids.Email)
|
||||||
|
ids.DisplayName = ptr.Val(team.GetDisplayName())
|
||||||
|
|
||||||
|
channel, err := ac.Channels().
|
||||||
|
GetChannelByName(
|
||||||
|
ctx,
|
||||||
|
ids.ID,
|
||||||
|
"Test")
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
require.Equal(t, "Test", ptr.Val(channel.GetDisplayName()))
|
||||||
|
|
||||||
|
ids.TestContainerID = ptr.Val(channel.GetId())
|
||||||
|
|
||||||
|
site, err := ac.Groups().GetRootSite(ctx, gid)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.RootSite.ID = ptr.Val(site.GetId())
|
||||||
|
ids.RootSite.WebURL = ptr.Val(site.GetWebUrl())
|
||||||
|
ids.RootSite.DisplayName = ptr.Val(site.GetDisplayName())
|
||||||
|
ids.RootSite.Provider = idname.NewProvider(ids.RootSite.ID, ids.RootSite.WebURL)
|
||||||
|
|
||||||
|
drive, err := ac.Sites().GetDefaultDrive(ctx, ids.RootSite.ID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.RootSite.DriveID = ptr.Val(drive.GetId())
|
||||||
|
|
||||||
|
rootFolder, err := ac.Drives().GetRootFolder(ctx, ids.RootSite.DriveID)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
ids.RootSite.DriveRootFolderID = ptr.Val(rootFolder.GetId())
|
||||||
|
}
|
||||||
154
src/internal/tester/its/m365_test.go
Normal file
154
src/internal/tester/its/m365_test.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package its
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
|
storeTD "github.com/alcionai/corso/src/pkg/storage/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type M365IntgSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestM365IntgSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &M365IntgSuite{
|
||||||
|
Suite: tester.NewIntegrationSuite(
|
||||||
|
t,
|
||||||
|
[][]string{tconfig.M365AcctCredEnvs, storeTD.AWSStorageCredEnvs}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *M365IntgSuite) TestGetM365() {
|
||||||
|
t := suite.T()
|
||||||
|
result := GetM365(t)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, result.Acct)
|
||||||
|
assert.NotEmpty(t, result.Creds)
|
||||||
|
assert.NotEmpty(t, result.AC)
|
||||||
|
assert.NotEmpty(t, result.GockAC)
|
||||||
|
assert.NotEmpty(t, result.TenantID)
|
||||||
|
|
||||||
|
var (
|
||||||
|
none = []string{}
|
||||||
|
expectSite = []string{id, weburl, provider, displayname, driveid, driverootfolderid}
|
||||||
|
expectRootSite = []string{id, weburl, provider, displayname, driveid, driverootfolderid}
|
||||||
|
expectGroup = []string{id, email, provider, displayname, testcontainerid}
|
||||||
|
expectUser = []string{id, email, provider, displayname, driveid, driverootfolderid}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertIDs(t, result.Site, expectSite, none)
|
||||||
|
assertIDs(t, result.SecondarySite, expectSite, none)
|
||||||
|
assertIDs(t, result.Group, expectGroup, expectRootSite)
|
||||||
|
assertIDs(t, result.SecondaryGroup, expectGroup, expectRootSite)
|
||||||
|
assertIDs(t, result.User, expectUser, none)
|
||||||
|
assertIDs(t, result.SecondaryUser, expectUser, none)
|
||||||
|
assertIDs(t, result.TertiaryUser, expectUser, none)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
provider = "provider"
|
||||||
|
id = "id"
|
||||||
|
email = "email"
|
||||||
|
displayname = "displayname"
|
||||||
|
driveid = "driveid"
|
||||||
|
driverootfolderid = "driverootfolderid"
|
||||||
|
testcontainerid = "testcontainerid"
|
||||||
|
weburl = "weburl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertIDs(
|
||||||
|
t *testing.T,
|
||||||
|
ids IDs,
|
||||||
|
expect []string,
|
||||||
|
expectRootSite []string,
|
||||||
|
) {
|
||||||
|
assert.NotEmpty(t, ids)
|
||||||
|
|
||||||
|
if slices.Contains(expect, provider) {
|
||||||
|
assert.NotNil(t, ids.Provider)
|
||||||
|
assert.NotEmpty(t, ids.Provider.ID())
|
||||||
|
assert.NotEmpty(t, ids.Provider.Name())
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, ids.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expect, id) {
|
||||||
|
assert.NotEmpty(t, ids.ID)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expect, email) {
|
||||||
|
assert.NotEmpty(t, ids.Email)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expect, driveid) {
|
||||||
|
assert.NotEmpty(t, ids.DriveID)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.DriveID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expect, driverootfolderid) {
|
||||||
|
assert.NotEmpty(t, ids.DriveRootFolderID)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.DriveRootFolderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expect, testcontainerid) {
|
||||||
|
assert.NotEmpty(t, ids.TestContainerID)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.TestContainerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expect, weburl) {
|
||||||
|
assert.NotEmpty(t, ids.WebURL)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.WebURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expectRootSite, provider) {
|
||||||
|
assert.NotNil(t, ids.RootSite.Provider)
|
||||||
|
assert.NotEmpty(t, ids.RootSite.Provider.ID())
|
||||||
|
assert.NotEmpty(t, ids.RootSite.Provider.Name())
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, ids.RootSite.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expectRootSite, id) {
|
||||||
|
assert.NotEmpty(t, ids.RootSite.ID)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.RootSite.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expectRootSite, driveid) {
|
||||||
|
assert.NotEmpty(t, ids.RootSite.DriveID)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.RootSite.DriveID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expectRootSite, displayname) {
|
||||||
|
assert.NotEmpty(t, ids.RootSite.DisplayName)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.RootSite.DisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expectRootSite, driverootfolderid) {
|
||||||
|
assert.NotEmpty(t, ids.RootSite.DriveRootFolderID)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.RootSite.DriveRootFolderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(expectRootSite, weburl) {
|
||||||
|
assert.NotEmpty(t, ids.RootSite.WebURL)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, ids.RootSite.WebURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/internal/tester/its/resourceService.go
Normal file
30
src/internal/tester/its/resourceService.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package its
|
||||||
|
|
||||||
|
import "github.com/alcionai/corso/src/pkg/path"
|
||||||
|
|
||||||
|
type ResourceServicer interface {
|
||||||
|
Resource() IDs
|
||||||
|
Service() path.ServiceType
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ResourceServicer = resourceAndService{}
|
||||||
|
|
||||||
|
type resourceAndService struct {
|
||||||
|
protectedResource IDs
|
||||||
|
serviceType path.ServiceType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ras resourceAndService) Resource() IDs {
|
||||||
|
return ras.protectedResource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ras resourceAndService) Service() path.ServiceType {
|
||||||
|
return ras.serviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResourceService(r IDs, s path.ServiceType) ResourceServicer {
|
||||||
|
return &resourceAndService{
|
||||||
|
protectedResource: r,
|
||||||
|
serviceType: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -37,10 +37,10 @@ func NewFakeM365Account(t *testing.T) account.Account {
|
|||||||
account.ProviderM365,
|
account.ProviderM365,
|
||||||
account.M365Config{
|
account.M365Config{
|
||||||
M365: credentials.M365{
|
M365: credentials.M365{
|
||||||
AzureClientID: "12345",
|
AzureClientID: "client-12345",
|
||||||
AzureClientSecret: "abcde",
|
AzureClientSecret: "secret-abcde",
|
||||||
},
|
},
|
||||||
AzureTenantID: "09876",
|
AzureTenantID: "tenant-09876",
|
||||||
})
|
})
|
||||||
require.NoError(t, err, "initializing mock account", clues.ToCore(err))
|
require.NoError(t, err, "initializing mock account", clues.ToCore(err))
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/cli/flags"
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
"github.com/alcionai/corso/src/internal/common/str"
|
"github.com/alcionai/corso/src/internal/common/str"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester/its"
|
||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/control/repository"
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
@ -399,6 +400,7 @@ func (suite *ConfigSuite) TestReadFromFlags() {
|
|||||||
|
|
||||||
type ConfigIntegrationSuite struct {
|
type ConfigIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
|
m365 its.M365IntgTestSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigIntegrationSuite(t *testing.T) {
|
func TestConfigIntegrationSuite(t *testing.T) {
|
||||||
@ -407,6 +409,10 @@ func TestConfigIntegrationSuite(t *testing.T) {
|
|||||||
[][]string{storeTD.AWSStorageCredEnvs, tconfig.M365AcctCredEnvs})})
|
[][]string{storeTD.AWSStorageCredEnvs, tconfig.M365AcctCredEnvs})})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *ConfigIntegrationSuite) SetupSuite() {
|
||||||
|
suite.m365 = its.GetM365(suite.T())
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *ConfigIntegrationSuite) TestGetStorageAndAccount() {
|
func (suite *ConfigIntegrationSuite) TestGetStorageAndAccount() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
vpr := viper.New()
|
vpr := viper.New()
|
||||||
@ -418,7 +424,6 @@ func (suite *ConfigIntegrationSuite) TestGetStorageAndAccount() {
|
|||||||
bkt = "get-storage-and-account-bucket"
|
bkt = "get-storage-and-account-bucket"
|
||||||
end = "https://get-storage-and-account.com"
|
end = "https://get-storage-and-account.com"
|
||||||
pfx = "get-storage-and-account-prefix/"
|
pfx = "get-storage-and-account-prefix/"
|
||||||
tid = "3a2faa4e-a882-445c-9d27-f552ef189381"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configure viper to read test config file
|
// Configure viper to read test config file
|
||||||
@ -434,9 +439,10 @@ func (suite *ConfigIntegrationSuite) TestGetStorageAndAccount() {
|
|||||||
DoNotVerifyTLS: true,
|
DoNotVerifyTLS: true,
|
||||||
DoNotUseTLS: true,
|
DoNotUseTLS: true,
|
||||||
}
|
}
|
||||||
m365 := account.M365Config{AzureTenantID: tid}
|
|
||||||
|
|
||||||
err = writeRepoConfigWithViper(vpr, s3Cfg, m365, repository.Options{}, "repoid")
|
creds := suite.m365.Creds
|
||||||
|
|
||||||
|
err = writeRepoConfigWithViper(vpr, s3Cfg, creds, repository.Options{}, "repoid")
|
||||||
require.NoError(t, err, "writing repo config", clues.ToCore(err))
|
require.NoError(t, err, "writing repo config", clues.ToCore(err))
|
||||||
|
|
||||||
require.Equal(
|
require.Equal(
|
||||||
@ -484,13 +490,12 @@ func (suite *ConfigIntegrationSuite) TestGetStorageAndAccount_noFileOnlyOverride
|
|||||||
bkt = "get-storage-and-account-no-file-bucket"
|
bkt = "get-storage-and-account-no-file-bucket"
|
||||||
end = "https://get-storage-and-account.com/no-file"
|
end = "https://get-storage-and-account.com/no-file"
|
||||||
pfx = "get-storage-and-account-no-file-prefix/"
|
pfx = "get-storage-and-account-no-file-prefix/"
|
||||||
tid = "88f8522b-18e4-4d0f-b514-2d7b34d4c5a1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
m365 := account.M365Config{AzureTenantID: tid}
|
creds := suite.m365.Creds
|
||||||
|
|
||||||
overrides := map[string]string{
|
overrides := map[string]string{
|
||||||
account.AzureTenantID: tid,
|
account.AzureTenantID: suite.m365.TenantID,
|
||||||
account.AccountProviderTypeKey: account.ProviderM365.String(),
|
account.AccountProviderTypeKey: account.ProviderM365.String(),
|
||||||
storage.Bucket: bkt,
|
storage.Bucket: bkt,
|
||||||
storage.Endpoint: end,
|
storage.Endpoint: end,
|
||||||
@ -519,7 +524,7 @@ func (suite *ConfigIntegrationSuite) TestGetStorageAndAccount_noFileOnlyOverride
|
|||||||
|
|
||||||
readM365, err := cfg.Account.M365Config()
|
readM365, err := cfg.Account.M365Config()
|
||||||
require.NoError(t, err, "reading m365 config from account", clues.ToCore(err))
|
require.NoError(t, err, "reading m365 config from account", clues.ToCore(err))
|
||||||
assert.Equal(t, readM365.AzureTenantID, m365.AzureTenantID)
|
assert.Equal(t, readM365.AzureTenantID, creds.AzureTenantID)
|
||||||
assert.Equal(t, readM365.AzureClientID, os.Getenv(credentials.AzureClientID))
|
assert.Equal(t, readM365.AzureClientID, os.Getenv(credentials.AzureClientID))
|
||||||
assert.Equal(t, readM365.AzureClientSecret, os.Getenv(credentials.AzureClientSecret))
|
assert.Equal(t, readM365.AzureClientSecret, os.Getenv(credentials.AzureClientSecret))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,11 @@ type Options struct {
|
|||||||
// backup data until the set limits without paying attention to what the other
|
// backup data until the set limits without paying attention to what the other
|
||||||
// had already backed up.
|
// had already backed up.
|
||||||
PreviewLimits PreviewItemLimits `json:"previewItemLimits"`
|
PreviewLimits PreviewItemLimits `json:"previewItemLimits"`
|
||||||
|
|
||||||
|
// specifying a resource tuple in this map allows that resource to produce
|
||||||
|
// a Skip instead of a recoverable error in case of a failure due to 503 when
|
||||||
|
// retrieving calendar event item data.
|
||||||
|
SkipEventsOnInstance503ForResources map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RateLimiter is the set of options applied to any external service facing rate
|
// RateLimiter is the set of options applied to any external service facing rate
|
||||||
|
|||||||
@ -66,6 +66,12 @@ var (
|
|||||||
// about what it sounds like: we tried to look for a backup by ID, but the
|
// about what it sounds like: we tried to look for a backup by ID, but the
|
||||||
// storage layer couldn't find anything for that ID.
|
// storage layer couldn't find anything for that ID.
|
||||||
ErrBackupNotFound = &Err{msg: "backup not found"}
|
ErrBackupNotFound = &Err{msg: "backup not found"}
|
||||||
|
// basically "internal server error". But not internal issues. We only return this
|
||||||
|
// when a downstream service (ex: graph api) responds with a 5xx style error.
|
||||||
|
// Note: producers may not funnel all 5xx errors in this umbrella, because
|
||||||
|
// different cases (ex: `StatusHTTPVersionNotSupported`) may need more specific
|
||||||
|
// attention and handling than standard gateway outages or service issues.
|
||||||
|
ErrDownstreamServerError = &Err{msg: "server error in downstream service"}
|
||||||
// a catch-all for downstream api auth issues. doesn't matter which api.
|
// a catch-all for downstream api auth issues. doesn't matter which api.
|
||||||
ErrInsufficientAuthorization = &Err{msg: "insufficient authorization"}
|
ErrInsufficientAuthorization = &Err{msg: "insufficient authorization"}
|
||||||
// happens when we look up something using an identifier other than a canonical ID
|
// happens when we look up something using an identifier other than a canonical ID
|
||||||
@ -116,3 +122,27 @@ func As(err error) (*Err, bool) {
|
|||||||
|
|
||||||
return ce, ok
|
return ce, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Labels
|
||||||
|
//
|
||||||
|
// In some cases we like to attach well known labels to errors for additional
|
||||||
|
// metadata or metrics or other associations. Labeling differs from error
|
||||||
|
// typing or identification because a label won't explain the cause or clearly
|
||||||
|
// contextualize an error. Labels are tags for their own sake.
|
||||||
|
//
|
||||||
|
// Therefore, labels are expressly not for error identification. IE: if you
|
||||||
|
// see the check `if clues.HasLabel(err, labelFoo)` in place of
|
||||||
|
// `if errors.Is(err, errFoo)`, that's a red flag.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const (
|
||||||
|
// add this label when we might need to further investigate the cause of the
|
||||||
|
// error. For example, in the graph api layer we try to categorize errors
|
||||||
|
// by their specific identity, such as "the resource was locked out". If
|
||||||
|
// we're unsuccessful, we can still fall back to the more generic error code,
|
||||||
|
// "403 forbidden". But it tradeoff, we may end up catching (and gracefully
|
||||||
|
// handling) 403s, but not identifying an underlying root issue. This label
|
||||||
|
// is here to say, "maybe you should look for the reason why this happened".
|
||||||
|
LabelRootCauseUnknown = "root-cause-unknown"
|
||||||
|
)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/observe"
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConsumeExportCollections(
|
func ConsumeExportCollections(
|
||||||
@ -19,6 +20,10 @@ func ConsumeExportCollections(
|
|||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
) error {
|
) error {
|
||||||
el := errs.Local()
|
el := errs.Local()
|
||||||
|
counted := 0
|
||||||
|
log := logger.Ctx(ctx).
|
||||||
|
With("export_location", exportLocation,
|
||||||
|
"collection_count", len(expColl))
|
||||||
|
|
||||||
for _, col := range expColl {
|
for _, col := range expColl {
|
||||||
if el.Failure() != nil {
|
if el.Failure() != nil {
|
||||||
@ -29,6 +34,13 @@ func ConsumeExportCollections(
|
|||||||
ictx := clues.Add(ctx, "dir_name", folder)
|
ictx := clues.Add(ctx, "dir_name", folder)
|
||||||
|
|
||||||
for item := range col.Items(ictx) {
|
for item := range col.Items(ictx) {
|
||||||
|
counted++
|
||||||
|
|
||||||
|
// Log every 1000 items that are processed
|
||||||
|
if counted%1000 == 0 {
|
||||||
|
log.Infow("progress writing export items", "count_items", counted)
|
||||||
|
}
|
||||||
|
|
||||||
if item.Error != nil {
|
if item.Error != nil {
|
||||||
el.AddRecoverable(ictx, clues.Wrap(item.Error, "getting item"))
|
el.AddRecoverable(ictx, clues.Wrap(item.Error, "getting item"))
|
||||||
continue
|
continue
|
||||||
@ -42,6 +54,8 @@ func ConsumeExportCollections(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infow("completed writing export items", "count_items", counted)
|
||||||
|
|
||||||
return el.Failure()
|
return el.Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,30 +12,39 @@ type AddSkipper interface {
|
|||||||
AddSkip(ctx context.Context, s *Skipped)
|
AddSkip(ctx context.Context, s *Skipped)
|
||||||
}
|
}
|
||||||
|
|
||||||
// skipCause identifies the well-known conditions to Skip an item. It is
|
// SkipCause identifies the well-known conditions to Skip an item. It is
|
||||||
// important that skip cause enumerations do not overlap with general error
|
// important that skip cause enumerations do not overlap with general error
|
||||||
// handling. Skips must be well known, well documented, and consistent.
|
// handling. Skips must be well known, well documented, and consistent.
|
||||||
// Transient failures, undocumented or unknown conditions, and arbitrary
|
// Transient failures, undocumented or unknown conditions, and arbitrary
|
||||||
// handling should never produce a skipped item. Those cases should get
|
// handling should never produce a skipped item. Those cases should get
|
||||||
// handled as normal errors.
|
// handled as normal errors.
|
||||||
type skipCause string
|
type SkipCause string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SkipMalware identifies a malware detection case. Files that graph
|
// SkipMalware identifies a malware detection case. Files that graph
|
||||||
// api identifies as malware cannot be downloaded or uploaded, and will
|
// api identifies as malware cannot be downloaded or uploaded, and will
|
||||||
// permanently fail any attempts to backup or restore.
|
// permanently fail any attempts to backup or restore.
|
||||||
SkipMalware skipCause = "malware_detected"
|
SkipMalware SkipCause = "malware_detected"
|
||||||
|
|
||||||
// SkipOneNote identifies that a file was skipped because it
|
// SkipOneNote identifies that a file was skipped because it
|
||||||
// was a OneNote file that remains inaccessible (503 server response)
|
// was a OneNote file that remains inaccessible (503 server response)
|
||||||
// regardless of the number of retries.
|
// regardless of the number of retries.
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
// https://support.microsoft.com/en-us/office/restrictions-and-limitations-in-onedrive-and-sharepoint-64883a5d-228e-48f5-b3d2-eb39e07630fa#onenotenotebooks
|
// https://support.microsoft.com/en-us/office/restrictions-and-limitations-in-onedrive-and-sharepoint-64883a5d-228e-48f5-b3d2-eb39e07630fa#onenotenotebooks
|
||||||
SkipOneNote skipCause = "inaccessible_one_note_file"
|
SkipOneNote SkipCause = "inaccessible_one_note_file"
|
||||||
|
|
||||||
// SkipInvalidRecipients identifies that an email was skipped because Exchange
|
// SkipInvalidRecipients identifies that an email was skipped because Exchange
|
||||||
// believes it is not valid and fails any attempt to read it.
|
// believes it is not valid and fails any attempt to read it.
|
||||||
SkipInvalidRecipients skipCause = "invalid_recipients_email"
|
SkipInvalidRecipients SkipCause = "invalid_recipients_email"
|
||||||
|
|
||||||
|
// SkipCorruptData identifies that an email was skipped because graph reported
|
||||||
|
// that the email data was corrupt and failed all attempts to read it.
|
||||||
|
SkipCorruptData SkipCause = "corrupt_data"
|
||||||
|
|
||||||
|
// SkipKnownEventInstance503s identifies cases where we have a pre-configured list
|
||||||
|
// of event IDs where the events are known to fail with a 503 due to there being
|
||||||
|
// too many instances to retrieve from graph api.
|
||||||
|
SkipKnownEventInstance503s SkipCause = "known_event_instance_503"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ print.Printable = &Skipped{}
|
var _ print.Printable = &Skipped{}
|
||||||
@ -66,7 +75,7 @@ func (s *Skipped) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasCause compares the underlying cause against the parameter.
|
// HasCause compares the underlying cause against the parameter.
|
||||||
func (s *Skipped) HasCause(c skipCause) bool {
|
func (s *Skipped) HasCause(c SkipCause) bool {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -101,27 +110,27 @@ func (s Skipped) Values(bool) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ContainerSkip produces a Container-kind Item for tracking skipped items.
|
// ContainerSkip produces a Container-kind Item for tracking skipped items.
|
||||||
func ContainerSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped {
|
func ContainerSkip(cause SkipCause, namespace, id, name string, addtl map[string]any) *Skipped {
|
||||||
return itemSkip(ContainerType, cause, namespace, id, name, addtl)
|
return itemSkip(ContainerType, cause, namespace, id, name, addtl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailSkip produces a Email-kind Item for tracking skipped items.
|
// EmailSkip produces a Email-kind Item for tracking skipped items.
|
||||||
func EmailSkip(cause skipCause, user, id string, addtl map[string]any) *Skipped {
|
func EmailSkip(cause SkipCause, user, id string, addtl map[string]any) *Skipped {
|
||||||
return itemSkip(EmailType, cause, user, id, "", addtl)
|
return itemSkip(EmailType, cause, user, id, "", addtl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileSkip produces a File-kind Item for tracking skipped items.
|
// FileSkip produces a File-kind Item for tracking skipped items.
|
||||||
func FileSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped {
|
func FileSkip(cause SkipCause, namespace, id, name string, addtl map[string]any) *Skipped {
|
||||||
return itemSkip(FileType, cause, namespace, id, name, addtl)
|
return itemSkip(FileType, cause, namespace, id, name, addtl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnwerSkip produces a ResourceOwner-kind Item for tracking skipped items.
|
// OnwerSkip produces a ResourceOwner-kind Item for tracking skipped items.
|
||||||
func OwnerSkip(cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped {
|
func OwnerSkip(cause SkipCause, namespace, id, name string, addtl map[string]any) *Skipped {
|
||||||
return itemSkip(ResourceOwnerType, cause, namespace, id, name, addtl)
|
return itemSkip(ResourceOwnerType, cause, namespace, id, name, addtl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// itemSkip produces a Item of the provided type for tracking skipped items.
|
// itemSkip produces a Item of the provided type for tracking skipped items.
|
||||||
func itemSkip(t ItemType, cause skipCause, namespace, id, name string, addtl map[string]any) *Skipped {
|
func itemSkip(t ItemType, cause SkipCause, namespace, id, name string, addtl map[string]any) *Skipped {
|
||||||
return &Skipped{
|
return &Skipped{
|
||||||
Item: Item{
|
Item: Item{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user